mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-19 06:41:47 -05:00
BFM demod: working RDS support
This commit is contained in:
parent
6782c753ad
commit
48855bfb63
@ -6,6 +6,8 @@ set(bfm_SOURCES
|
|||||||
bfmplugin.cpp
|
bfmplugin.cpp
|
||||||
rdsdemod.cpp
|
rdsdemod.cpp
|
||||||
rdsdecoder.cpp
|
rdsdecoder.cpp
|
||||||
|
rdsparser.cpp
|
||||||
|
rdstmc.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(bfm_HEADERS
|
set(bfm_HEADERS
|
||||||
@ -14,6 +16,8 @@ set(bfm_HEADERS
|
|||||||
bfmplugin.h
|
bfmplugin.h
|
||||||
rdsdemod.h
|
rdsdemod.h
|
||||||
rdsdecoder.h
|
rdsdecoder.h
|
||||||
|
rdsparser.h
|
||||||
|
rdstmc.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(bfm_FORMS
|
set(bfm_FORMS
|
||||||
|
@ -23,13 +23,15 @@
|
|||||||
#include "dsp/dspengine.h"
|
#include "dsp/dspengine.h"
|
||||||
#include "dsp/channelizer.h"
|
#include "dsp/channelizer.h"
|
||||||
#include "dsp/pidcontroller.h"
|
#include "dsp/pidcontroller.h"
|
||||||
|
#include "rdsparser.h"
|
||||||
|
|
||||||
#include "bfmdemod.h"
|
#include "bfmdemod.h"
|
||||||
|
|
||||||
MESSAGE_CLASS_DEFINITION(BFMDemod::MsgConfigureBFMDemod, Message)
|
MESSAGE_CLASS_DEFINITION(BFMDemod::MsgConfigureBFMDemod, Message)
|
||||||
|
|
||||||
BFMDemod::BFMDemod(SampleSink* sampleSink) :
|
BFMDemod::BFMDemod(SampleSink* sampleSink, RDSParser *rdsParser) :
|
||||||
m_sampleSink(sampleSink),
|
m_sampleSink(sampleSink),
|
||||||
|
m_rdsParser(rdsParser),
|
||||||
m_audioFifo(4, 250000),
|
m_audioFifo(4, 250000),
|
||||||
m_settingsMutex(QMutex::Recursive),
|
m_settingsMutex(QMutex::Recursive),
|
||||||
m_pilotPLL(19000/384000, 50/384000, 0.01),
|
m_pilotPLL(19000/384000, 50/384000, 0.01),
|
||||||
@ -145,7 +147,13 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
|
|||||||
|
|
||||||
if (m_rdsDemod.process(cr.real(), bit))
|
if (m_rdsDemod.process(cr.real(), bit))
|
||||||
{
|
{
|
||||||
m_rdsDecoder.frameSync(bit);
|
if (m_rdsDecoder.frameSync(bit))
|
||||||
|
{
|
||||||
|
if (m_rdsParser)
|
||||||
|
{
|
||||||
|
m_rdsParser->parseGroup(m_rdsDecoder.getGroup());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_interpolatorRDSDistanceRemain += m_interpolatorRDSDistance;
|
m_interpolatorRDSDistanceRemain += m_interpolatorRDSDistance;
|
||||||
|
@ -35,9 +35,11 @@
|
|||||||
|
|
||||||
#define rfFilterFftLength 1024
|
#define rfFilterFftLength 1024
|
||||||
|
|
||||||
|
class RDSParser;
|
||||||
|
|
||||||
class BFMDemod : public SampleSink {
|
class BFMDemod : public SampleSink {
|
||||||
public:
|
public:
|
||||||
BFMDemod(SampleSink* sampleSink);
|
BFMDemod(SampleSink* sampleSink, RDSParser* rdsParser);
|
||||||
virtual ~BFMDemod();
|
virtual ~BFMDemod();
|
||||||
|
|
||||||
void configure(MessageQueue* messageQueue,
|
void configure(MessageQueue* messageQueue,
|
||||||
@ -194,6 +196,7 @@ private:
|
|||||||
|
|
||||||
RDSDemod m_rdsDemod;
|
RDSDemod m_rdsDemod;
|
||||||
RDSDecoder m_rdsDecoder;
|
RDSDecoder m_rdsDecoder;
|
||||||
|
RDSParser *m_rdsParser;
|
||||||
|
|
||||||
LowPassFilterRC m_deemphasisFilterX;
|
LowPassFilterRC m_deemphasisFilterX;
|
||||||
LowPassFilterRC m_deemphasisFilterY;
|
LowPassFilterRC m_deemphasisFilterY;
|
||||||
|
@ -280,7 +280,7 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, QWidget* parent) :
|
|||||||
connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked()));
|
connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked()));
|
||||||
|
|
||||||
m_spectrumVis = new SpectrumVis(ui->glSpectrum);
|
m_spectrumVis = new SpectrumVis(ui->glSpectrum);
|
||||||
m_bfmDemod = new BFMDemod(m_spectrumVis);
|
m_bfmDemod = new BFMDemod(m_spectrumVis, &m_rdsParser);
|
||||||
m_channelizer = new Channelizer(m_bfmDemod);
|
m_channelizer = new Channelizer(m_bfmDemod);
|
||||||
m_threadedChannelizer = new ThreadedSampleSink(m_channelizer, this);
|
m_threadedChannelizer = new ThreadedSampleSink(m_channelizer, this);
|
||||||
connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged()));
|
connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged()));
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "plugin/plugingui.h"
|
#include "plugin/plugingui.h"
|
||||||
#include "dsp/channelmarker.h"
|
#include "dsp/channelmarker.h"
|
||||||
#include "dsp/movingaverage.h"
|
#include "dsp/movingaverage.h"
|
||||||
|
#include "rdsparser.h"
|
||||||
|
|
||||||
class PluginAPI;
|
class PluginAPI;
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ private:
|
|||||||
ThreadedSampleSink* m_threadedChannelizer;
|
ThreadedSampleSink* m_threadedChannelizer;
|
||||||
Channelizer* m_channelizer;
|
Channelizer* m_channelizer;
|
||||||
SpectrumVis* m_spectrumVis;
|
SpectrumVis* m_spectrumVis;
|
||||||
|
RDSParser m_rdsParser;
|
||||||
|
|
||||||
BFMDemod* m_bfmDemod;
|
BFMDemod* m_bfmDemod;
|
||||||
MovingAverage<Real> m_channelPowerDbAvg;
|
MovingAverage<Real> m_channelPowerDbAvg;
|
||||||
|
@ -45,8 +45,9 @@ RDSDecoder::~RDSDecoder()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void RDSDecoder::frameSync(bool bit)
|
bool RDSDecoder::frameSync(bool bit)
|
||||||
{
|
{
|
||||||
|
bool group_ready = false;
|
||||||
unsigned int reg_syndrome;
|
unsigned int reg_syndrome;
|
||||||
unsigned long bit_distance, block_distance;
|
unsigned long bit_distance, block_distance;
|
||||||
unsigned int block_calculated_crc, block_received_crc, checkword, dataword;
|
unsigned int block_calculated_crc, block_received_crc, checkword, dataword;
|
||||||
@ -170,7 +171,7 @@ void RDSDecoder::frameSync(bool bit)
|
|||||||
|
|
||||||
if (m_groupGoodBlocksCounter == 5)
|
if (m_groupGoodBlocksCounter == 5)
|
||||||
{
|
{
|
||||||
//decode_group(group); TODO: pass on to the group parser
|
group_ready = true; //decode_group(group); pass on to the group parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +206,8 @@ void RDSDecoder::frameSync(bool bit)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_bitCounter++;
|
m_bitCounter++;
|
||||||
|
|
||||||
|
return group_ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////// HELPER FUNTIONS /////////////////////////
|
////////////////////////// HELPER FUNTIONS /////////////////////////
|
||||||
|
@ -24,7 +24,8 @@ public:
|
|||||||
RDSDecoder();
|
RDSDecoder();
|
||||||
~RDSDecoder();
|
~RDSDecoder();
|
||||||
|
|
||||||
void frameSync(bool bit);
|
bool frameSync(bool bit);
|
||||||
|
unsigned int *getGroup() { return m_group; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
unsigned int calc_syndrome(unsigned long message, unsigned char mlen);
|
unsigned int calc_syndrome(unsigned long message, unsigned char mlen);
|
||||||
|
@ -23,11 +23,10 @@
|
|||||||
#include "rdsdemod.h"
|
#include "rdsdemod.h"
|
||||||
|
|
||||||
const Real RDSDemod::m_pllBeta = 50;
|
const Real RDSDemod::m_pllBeta = 50;
|
||||||
const int RDSDemod::m_udpSize = 1472;
|
|
||||||
const Real RDSDemod::m_fsc = 1187.5;
|
const Real RDSDemod::m_fsc = 1187.5;
|
||||||
|
|
||||||
RDSDemod::RDSDemod() :
|
RDSDemod::RDSDemod()
|
||||||
m_udpDebug(this, 1472, 9995)
|
// : m_udpDebug(this, 1472, 9995) // UDP debug
|
||||||
{
|
{
|
||||||
m_srate = 250000;
|
m_srate = 250000;
|
||||||
|
|
||||||
@ -60,7 +59,7 @@ bool RDSDemod::process(Real demod, bool& bit)
|
|||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
m_udpDebug.write(m_parms.lo_clock * m_parms.subcarr_bb[0]);
|
//m_udpDebug.write(m_parms.lo_clock * m_parms.subcarr_bb[0]); // UDP debug
|
||||||
|
|
||||||
// Subcarrier downmix & phase recovery
|
// Subcarrier downmix & phase recovery
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
#define PLUGINS_CHANNEL_BFM_RDSDEMOD_H_
|
#define PLUGINS_CHANNEL_BFM_RDSDEMOD_H_
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "util/udpsink.h"
|
//#include "util/udpsink.h" // UDP debug
|
||||||
|
|
||||||
#include "dsp/dsptypes.h"
|
#include "dsp/dsptypes.h"
|
||||||
|
|
||||||
@ -69,9 +69,8 @@ private:
|
|||||||
|
|
||||||
int m_srate;
|
int m_srate;
|
||||||
|
|
||||||
UDPSink<Real> m_udpDebug;
|
//UDPSink<Real> m_udpDebug; // UDP debug
|
||||||
|
|
||||||
static const int m_udpSize;
|
|
||||||
static const Real m_pllBeta;
|
static const Real m_pllBeta;
|
||||||
static const Real m_fsc;
|
static const Real m_fsc;
|
||||||
};
|
};
|
||||||
|
863
plugins/channel/bfm/rdsparser.cpp
Normal file
863
plugins/channel/bfm/rdsparser.cpp
Normal file
@ -0,0 +1,863 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (C) 2015 F4EXB //
|
||||||
|
// written by Edouard Griffiths //
|
||||||
|
// //
|
||||||
|
// 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 as version 3 of the License, or //
|
||||||
|
// //
|
||||||
|
// 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 V3 for more details. //
|
||||||
|
// //
|
||||||
|
// You should have received a copy of the GNU General Public License //
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include "boost/format.hpp"
|
||||||
|
#include "rdsparser.h"
|
||||||
|
#include "rdstmc.h"
|
||||||
|
|
||||||
|
const unsigned int RDSParser::offset_pos[5] = {0,1,2,3,2};
|
||||||
|
const unsigned int RDSParser::offset_word[5] = {252,408,360,436,848};
|
||||||
|
const unsigned int RDSParser::syndrome[5] = {383,14,303,663,748};
|
||||||
|
const char * const RDSParser::offset_name[] = {"A","B","C","D","C'"};
|
||||||
|
|
||||||
|
/* page 77, Annex F in the standard */
|
||||||
|
const std::string RDSParser::pty_table[32] = {
|
||||||
|
"None",
|
||||||
|
"News",
|
||||||
|
"Current Affairs",
|
||||||
|
"Information",
|
||||||
|
"Sport",
|
||||||
|
"Education",
|
||||||
|
"Drama",
|
||||||
|
"Cultures",
|
||||||
|
"Science",
|
||||||
|
"Varied Speech",
|
||||||
|
"Pop Music",
|
||||||
|
"Rock Music",
|
||||||
|
"Easy Listening",
|
||||||
|
"Light Classics M",
|
||||||
|
"Serious Classics",
|
||||||
|
"Other Music",
|
||||||
|
"Weather & Metr",
|
||||||
|
"Finance",
|
||||||
|
"Children’s Progs",
|
||||||
|
"Social Affairs",
|
||||||
|
"Religion",
|
||||||
|
"Phone In",
|
||||||
|
"Travel & Touring",
|
||||||
|
"Leisure & Hobby",
|
||||||
|
"Jazz Music",
|
||||||
|
"Country Music",
|
||||||
|
"National Music",
|
||||||
|
"Oldies Music",
|
||||||
|
"Folk Music",
|
||||||
|
"Documentary",
|
||||||
|
"Alarm Test",
|
||||||
|
"Alarm-Alarm!"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* page 71, Annex D, table D.1 in the standard */
|
||||||
|
const std::string RDSParser::pi_country_codes[15][5] = {
|
||||||
|
{"DE","GR","MA","__","MD"},
|
||||||
|
{"DZ","CY","CZ","IE","EE"},
|
||||||
|
{"AD","SM","PL","TR","__"},
|
||||||
|
{"IL","CH","VA","MK","__"},
|
||||||
|
{"IT","JO","SK","__","__"},
|
||||||
|
{"BE","FI","SY","__","UA"},
|
||||||
|
{"RU","LU","TN","__","__"},
|
||||||
|
{"PS","BG","__","NL","PT"},
|
||||||
|
{"AL","DK","LI","LV","SI"},
|
||||||
|
{"AT","GI","IS","LB","__"},
|
||||||
|
{"HU","IQ","MC","__","__"},
|
||||||
|
{"MT","GB","LT","HR","__"},
|
||||||
|
{"DE","LY","YU","__","__"},
|
||||||
|
{"__","RO","ES","SE","__"},
|
||||||
|
{"EG","FR","NO","BY","BA"}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* page 72, Annex D, table D.2 in the standard */
|
||||||
|
const std::string RDSParser::coverage_area_codes[16] = {
|
||||||
|
"Local",
|
||||||
|
"International",
|
||||||
|
"National",
|
||||||
|
"Supra-regional",
|
||||||
|
"Regional 1",
|
||||||
|
"Regional 2",
|
||||||
|
"Regional 3",
|
||||||
|
"Regional 4",
|
||||||
|
"Regional 5",
|
||||||
|
"Regional 6",
|
||||||
|
"Regional 7",
|
||||||
|
"Regional 8",
|
||||||
|
"Regional 9",
|
||||||
|
"Regional 10",
|
||||||
|
"Regional 11",
|
||||||
|
"Regional 12"
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string RDSParser::rds_group_acronyms[16] = {
|
||||||
|
"BASIC",
|
||||||
|
"PIN/SL",
|
||||||
|
"RT",
|
||||||
|
"AID",
|
||||||
|
"CT",
|
||||||
|
"TDC",
|
||||||
|
"IH",
|
||||||
|
"RP",
|
||||||
|
"TMC",
|
||||||
|
"EWS",
|
||||||
|
"___",
|
||||||
|
"___",
|
||||||
|
"___",
|
||||||
|
"___",
|
||||||
|
"EON",
|
||||||
|
"___"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* page 74, Annex E, table E.1 in the standard: that's the ASCII table!!! */
|
||||||
|
|
||||||
|
/* see page 84, Annex J in the standard */
|
||||||
|
const std::string RDSParser::language_codes[44] = {
|
||||||
|
"Unkown/not applicable",
|
||||||
|
"Albanian",
|
||||||
|
"Breton",
|
||||||
|
"Catalan",
|
||||||
|
"Croatian",
|
||||||
|
"Welsh",
|
||||||
|
"Czech",
|
||||||
|
"Danish",
|
||||||
|
"German",
|
||||||
|
"English",
|
||||||
|
"Spanish",
|
||||||
|
"Esperanto",
|
||||||
|
"Estonian",
|
||||||
|
"Basque",
|
||||||
|
"Faroese",
|
||||||
|
"French",
|
||||||
|
"Frisian",
|
||||||
|
"Irish",
|
||||||
|
"Gaelic",
|
||||||
|
"Galician",
|
||||||
|
"Icelandic",
|
||||||
|
"Italian",
|
||||||
|
"Lappish",
|
||||||
|
"Latin",
|
||||||
|
"Latvian",
|
||||||
|
"Luxembourgian",
|
||||||
|
"Lithuanian",
|
||||||
|
"Hungarian",
|
||||||
|
"Maltese",
|
||||||
|
"Dutch",
|
||||||
|
"Norwegian",
|
||||||
|
"Occitan",
|
||||||
|
"Polish",
|
||||||
|
"Portuguese",
|
||||||
|
"Romanian",
|
||||||
|
"Romansh",
|
||||||
|
"Serbian",
|
||||||
|
"Slovak",
|
||||||
|
"Slovene",
|
||||||
|
"Finnish",
|
||||||
|
"Swedish",
|
||||||
|
"Turkish",
|
||||||
|
"Flemish",
|
||||||
|
"Walloon"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* see page 12 in ISO 14819-1 */
|
||||||
|
const std::string RDSParser::tmc_duration[8][2] =
|
||||||
|
{
|
||||||
|
{"no duration given", "no duration given"},
|
||||||
|
{"15 minutes", "next few hours"},
|
||||||
|
{"30 minutes", "rest of the day"},
|
||||||
|
{"1 hour", "until tomorrow evening"},
|
||||||
|
{"2 hours", "rest of the week"},
|
||||||
|
{"3 hours", "end of next week"},
|
||||||
|
{"4 hours", "end of the month"},
|
||||||
|
{"rest of the day", "long period"}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* optional message content, data field lengths and labels
|
||||||
|
* see page 15 in ISO 14819-1 */
|
||||||
|
const int RDSParser::optional_content_lengths[16] = {3,3,5,5,5,8,8,8,8,11,16,16,16,16,0,0};
|
||||||
|
|
||||||
|
const std::string RDSParser::label_descriptions[16] = {
|
||||||
|
"Duration",
|
||||||
|
"Control code",
|
||||||
|
"Length of route affected",
|
||||||
|
"Speed limit advice",
|
||||||
|
"Quantifier",
|
||||||
|
"Quantifier",
|
||||||
|
"Supplementary information code",
|
||||||
|
"Explicit start time",
|
||||||
|
"Explicit stop time",
|
||||||
|
"Additional event",
|
||||||
|
"Detailed diversion instructions",
|
||||||
|
"Destination",
|
||||||
|
"RFU (Reserved for future use)",
|
||||||
|
"Cross linkage to source of problem, or another route",
|
||||||
|
"Separator",
|
||||||
|
"RFU (Reserved for future use)"
|
||||||
|
};
|
||||||
|
|
||||||
|
RDSParser::RDSParser()
|
||||||
|
{
|
||||||
|
std::memset(radiotext, ' ', sizeof(radiotext));
|
||||||
|
radiotext[sizeof(radiotext) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
RDSParser::~RDSParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::parseGroup(unsigned int *group)
|
||||||
|
{
|
||||||
|
unsigned int group_type = (unsigned int)((group[1] >> 12) & 0xf);
|
||||||
|
bool ab = (group[1] >> 11 ) & 0x1;
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::parseGroup:"
|
||||||
|
<< " type: " << group_type << (ab ? 'B' :'A')
|
||||||
|
<< " (" << rds_group_acronyms[group_type].c_str() << ")";
|
||||||
|
|
||||||
|
program_identification = group[0]; // "PI"
|
||||||
|
program_type = (group[1] >> 5) & 0x1f; // "PTY"
|
||||||
|
int pi_country_identification = (program_identification >> 12) & 0xf;
|
||||||
|
int pi_area_coverage = (program_identification >> 8) & 0xf;
|
||||||
|
unsigned char pi_program_reference_number = program_identification & 0xff;
|
||||||
|
|
||||||
|
std::string pistring = str(boost::format("%04X") % program_identification);
|
||||||
|
//send_message(0, pistring);
|
||||||
|
//send_message(2, pty_table[program_type]);
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::parseGroup:"
|
||||||
|
<< " PI:" << pistring.c_str()
|
||||||
|
<< " - " << "PTY:" << pty_table[program_type].c_str()
|
||||||
|
<< " (country:" << (pi_country_codes[pi_country_identification - 1][0]).c_str()
|
||||||
|
<< "/" << (pi_country_codes[pi_country_identification - 1][1]).c_str()
|
||||||
|
<< "/" << (pi_country_codes[pi_country_identification - 1][2]).c_str()
|
||||||
|
<< "/" << (pi_country_codes[pi_country_identification - 1][3]).c_str()
|
||||||
|
<< "/" << (pi_country_codes[pi_country_identification - 1][4]).c_str()
|
||||||
|
<< ", area:" << coverage_area_codes[pi_area_coverage].c_str()
|
||||||
|
<< ", program:" << int(pi_program_reference_number) << ")";
|
||||||
|
|
||||||
|
switch (group_type) {
|
||||||
|
case 0:
|
||||||
|
decode_type0(group, ab);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
decode_type1(group, ab);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
decode_type2(group, ab);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
decode_type3(group, ab);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
decode_type4(group, ab);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
decode_type5(group, ab);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
decode_type6(group, ab);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
decode_type7(group, ab);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
decode_type8(group, ab);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
decode_type9(group, ab);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
decode_type10(group, ab);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
decode_type11(group, ab);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
decode_type12(group, ab);
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
decode_type13(group, ab);
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
decode_type14(group, ab);
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
decode_type15(group, ab);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define HEX(a) std::hex << std::setfill('0') << std::setw(4) << long(a) << std::dec
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
dout << " " << HEX(group[i]);
|
||||||
|
}
|
||||||
|
dout << std::endl;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BASIC TUNING: see page 21 of the standard */
|
||||||
|
void RDSParser::decode_type0(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
unsigned int af_code_1 = 0;
|
||||||
|
unsigned int af_code_2 = 0;
|
||||||
|
unsigned int no_af = 0;
|
||||||
|
double af_1 = 0;
|
||||||
|
double af_2 = 0;
|
||||||
|
char flagstring[8] = "0000000";
|
||||||
|
|
||||||
|
traffic_program = (group[1] >> 10) & 0x01; // "TP"
|
||||||
|
traffic_announcement = (group[1] >> 4) & 0x01; // "TA"
|
||||||
|
music_speech = (group[1] >> 3) & 0x01; // "MuSp"
|
||||||
|
|
||||||
|
bool decoder_control_bit = (group[1] >> 2) & 0x01; // "DI"
|
||||||
|
unsigned char segment_address = group[1] & 0x03; // "DI segment"
|
||||||
|
|
||||||
|
program_service_name[segment_address * 2] = (group[3] >> 8) & 0xff;
|
||||||
|
program_service_name[segment_address * 2 + 1] = group[3] & 0xff;
|
||||||
|
|
||||||
|
/* see page 41, table 9 of the standard */
|
||||||
|
switch (segment_address) {
|
||||||
|
case 0:
|
||||||
|
mono_stereo=decoder_control_bit;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
artificial_head=decoder_control_bit;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
compressed=decoder_control_bit;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
static_pty=decoder_control_bit;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
flagstring[0] = traffic_program ? '1' : '0';
|
||||||
|
flagstring[1] = traffic_announcement ? '1' : '0';
|
||||||
|
flagstring[2] = music_speech ? '1' : '0';
|
||||||
|
flagstring[3] = mono_stereo ? '1' : '0';
|
||||||
|
flagstring[4] = artificial_head ? '1' : '0';
|
||||||
|
flagstring[5] = compressed ? '1' : '0';
|
||||||
|
flagstring[6] = static_pty ? '1' : '0';
|
||||||
|
static std::string af_string;
|
||||||
|
|
||||||
|
if (!B)
|
||||||
|
{ // type 0A
|
||||||
|
af_code_1 = int(group[2] >> 8) & 0xff;
|
||||||
|
af_code_2 = int(group[2]) & 0xff;
|
||||||
|
af_1 = decode_af(af_code_1);
|
||||||
|
af_2 = decode_af(af_code_2);
|
||||||
|
|
||||||
|
if(af_1) {
|
||||||
|
no_af += 1;
|
||||||
|
}
|
||||||
|
if(af_2) {
|
||||||
|
no_af += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string af1_string;
|
||||||
|
std::string af2_string;
|
||||||
|
|
||||||
|
/* only AF1 => no_af==1, only AF2 => no_af==2, both AF1 and AF2 => no_af==3 */
|
||||||
|
if(no_af)
|
||||||
|
{
|
||||||
|
if(af_1 > 80e3) {
|
||||||
|
af1_string = str(boost::format("%2.2fMHz") % (af_1/1e3));
|
||||||
|
} else if((af_1<2e3)&&(af_1>100)) {
|
||||||
|
af1_string = str(boost::format("%ikHz") % int(af_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(af_2 > 80e3) {
|
||||||
|
af2_string = str(boost::format("%2.2fMHz") % (af_2/1e3));
|
||||||
|
} else if ((af_2 < 2e3) && (af_2 > 100)) {
|
||||||
|
af2_string = str(boost::format("%ikHz") % int(af_2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(no_af == 1) {
|
||||||
|
af_string = af1_string;
|
||||||
|
} else if(no_af == 2) {
|
||||||
|
af_string = af2_string;
|
||||||
|
} else if(no_af == 3) {
|
||||||
|
af_string = str(boost::format("%s, %s") % af1_string %af2_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type0: "
|
||||||
|
<< "\"" << std::string(program_service_name, 8).c_str()
|
||||||
|
<< "\" -" << (traffic_program ? "TP" : "!TP")
|
||||||
|
<< '-' << (traffic_announcement ? "TA" : "!TA")
|
||||||
|
<< '-' << (music_speech ? "Music" : "Speech")
|
||||||
|
<< '-' << (mono_stereo ? "MONO" : "STEREO")
|
||||||
|
<< " - AF:" << af_string.c_str();
|
||||||
|
|
||||||
|
//send_message(1, std::string(program_service_name, 8));
|
||||||
|
//send_message(3, flagstring);
|
||||||
|
//send_message(6, af_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
double RDSParser::decode_af(unsigned int af_code)
|
||||||
|
{
|
||||||
|
static unsigned int number_of_freqs = 0;
|
||||||
|
static bool vhf_or_lfmf = 0; // 0 = vhf, 1 = lf/mf
|
||||||
|
double alt_frequency = 0; // in kHz
|
||||||
|
|
||||||
|
if ((af_code == 0) || // not to be used
|
||||||
|
( af_code == 205) || // filler code
|
||||||
|
((af_code >= 206) && (af_code <= 223)) || // not assigned
|
||||||
|
( af_code == 224) || // No AF exists
|
||||||
|
( af_code >= 251)) // not assigned
|
||||||
|
{
|
||||||
|
number_of_freqs = 0;
|
||||||
|
alt_frequency = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((af_code >= 225) && (af_code <= 249)) // VHF frequencies follow
|
||||||
|
{
|
||||||
|
number_of_freqs = af_code - 224;
|
||||||
|
alt_frequency = 0;
|
||||||
|
vhf_or_lfmf = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (af_code == 250) // an LF/MF frequency follows
|
||||||
|
{
|
||||||
|
number_of_freqs = 1;
|
||||||
|
alt_frequency = 0;
|
||||||
|
vhf_or_lfmf = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((af_code > 0) && (af_code < 205) && vhf_or_lfmf) {
|
||||||
|
alt_frequency = 100.0 * (af_code + 875); // VHF (87.6-107.9MHz)
|
||||||
|
}
|
||||||
|
else if ((af_code > 0) && (af_code < 16) && !vhf_or_lfmf) {
|
||||||
|
alt_frequency = 153.0 + (af_code - 1) * 9; // LF (153-279kHz)
|
||||||
|
}
|
||||||
|
else if ((af_code > 15) && (af_code < 136) && !vhf_or_lfmf) {
|
||||||
|
alt_frequency = 531.0 + (af_code - 16) * 9 + 531; // MF (531-1602kHz)
|
||||||
|
}
|
||||||
|
|
||||||
|
return alt_frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type1(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
int ecc = 0;
|
||||||
|
int paging = 0;
|
||||||
|
char country_code = (group[0] >> 12) & 0x0f;
|
||||||
|
char radio_paging_codes = group[1] & 0x1f;
|
||||||
|
int variant_code = (group[2] >> 12) & 0x7;
|
||||||
|
unsigned int slow_labelling = group[2] & 0xfff;
|
||||||
|
int day = (int)((group[3] >> 11) & 0x1f);
|
||||||
|
int hour = (int)((group[3] >> 6) & 0x1f);
|
||||||
|
int minute = (int) (group[3] & 0x3f);
|
||||||
|
|
||||||
|
if (radio_paging_codes) {
|
||||||
|
qDebug() << "RDSParser::decode_type1: paging codes: " << int(radio_paging_codes) << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day || hour || minute) {
|
||||||
|
std::string s = str(boost::format("program item: %id, %i, %i ") % day % hour % minute);
|
||||||
|
qDebug() << "RDSParser::decode_type1: " << s.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!B)
|
||||||
|
{
|
||||||
|
switch (variant_code)
|
||||||
|
{
|
||||||
|
case 0: // paging + ecc
|
||||||
|
paging = (slow_labelling >> 8) & 0x0f;
|
||||||
|
ecc = slow_labelling & 0xff;
|
||||||
|
if (paging) {
|
||||||
|
qDebug() << "RDSParser::decode_type1: " << "paging: " << paging << " ";
|
||||||
|
}
|
||||||
|
if ((ecc > 223) && (ecc < 229)) {
|
||||||
|
qDebug() << "RDSParser::decode_type1: " << "extended country code: "
|
||||||
|
<< (pi_country_codes[country_code-1][ecc-224]).c_str();
|
||||||
|
} else {
|
||||||
|
qDebug() << "RDSParser::decode_type1: " << "invalid extended country code: " << ecc;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1: // TMC identification
|
||||||
|
qDebug() << "RDSParser::decode_type1: TMC identification code received";
|
||||||
|
break;
|
||||||
|
case 2: // Paging identification
|
||||||
|
qDebug() << "RDSParser::decode_type1: Paging identification code received";
|
||||||
|
break;
|
||||||
|
case 3: // language codes
|
||||||
|
if (slow_labelling < 44) {
|
||||||
|
qDebug() << "RDSParser::decode_type1: " << "language: " << language_codes[slow_labelling].c_str();
|
||||||
|
} else {
|
||||||
|
qDebug() << "RDSParser::decode_type1: " << "language: invalid language code " << slow_labelling;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type2(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
unsigned char text_segment_address_code = group[1] & 0x0f;
|
||||||
|
|
||||||
|
// when the A/B flag is toggled, flush your current radiotext
|
||||||
|
if (radiotext_AB_flag != ((group[1] >> 4) & 0x01))
|
||||||
|
{
|
||||||
|
std::memset(radiotext, ' ', sizeof(radiotext));
|
||||||
|
radiotext[sizeof(radiotext) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
radiotext_AB_flag = (group[1] >> 4) & 0x01;
|
||||||
|
|
||||||
|
if (!B)
|
||||||
|
{
|
||||||
|
radiotext[text_segment_address_code * 4 ] = (group[2] >> 8) & 0xff;
|
||||||
|
radiotext[text_segment_address_code * 4 + 1] = group[2] & 0xff;
|
||||||
|
radiotext[text_segment_address_code * 4 + 2] = (group[3] >> 8) & 0xff;
|
||||||
|
radiotext[text_segment_address_code * 4 + 3] = group[3] & 0xff;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
radiotext[text_segment_address_code * 2 ] = (group[3] >> 8) & 0xff;
|
||||||
|
radiotext[text_segment_address_code * 2 + 1] = group[3] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type2: " << "Radio Text " << (radiotext_AB_flag ? 'B' : 'A')
|
||||||
|
<< ": " << std::string(radiotext, sizeof(radiotext)).c_str();
|
||||||
|
|
||||||
|
//send_message(4,std::string(radiotext, sizeof(radiotext)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type3(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
if (B) {
|
||||||
|
qDebug() << "RDSParser::decode_type2: type 3B not implemented yet";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int application_group = (group[1] >> 1) & 0xf;
|
||||||
|
int group_type = group[1] & 0x1;
|
||||||
|
int message = group[2];
|
||||||
|
int aid = group[3];
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type3: aid group: " << application_group
|
||||||
|
<< " " << (group_type ? 'B' : 'A');
|
||||||
|
|
||||||
|
if ((application_group == 8) && (group_type == false))
|
||||||
|
{ // 8A
|
||||||
|
int variant_code = (message >> 14) & 0x3;
|
||||||
|
|
||||||
|
if (variant_code == 0)
|
||||||
|
{
|
||||||
|
int ltn = (message >> 6) & 0x3f; // location table number
|
||||||
|
bool afi = (message >> 5) & 0x1; // alternative freq. indicator
|
||||||
|
bool M = (message >> 4) & 0x1; // mode of transmission
|
||||||
|
bool I = (message >> 3) & 0x1; // international
|
||||||
|
bool N = (message >> 2) & 0x1; // national
|
||||||
|
bool R = (message >> 1) & 0x1; // regional
|
||||||
|
bool U = message & 0x1; // urban
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type3: location table: " << ltn << " - "
|
||||||
|
<< (afi ? "AFI-ON" : "AFI-OFF") << " - "
|
||||||
|
<< (M ? "enhanced mode" : "basic mode") << " - "
|
||||||
|
<< (I ? "international " : "")
|
||||||
|
<< (N ? "national " : "")
|
||||||
|
<< (R ? "regional " : "")
|
||||||
|
<< (U ? "urban" : "")
|
||||||
|
<< " aid: " << aid;
|
||||||
|
}
|
||||||
|
else if (variant_code==1)
|
||||||
|
{
|
||||||
|
int G = (message >> 12) & 0x3; // gap
|
||||||
|
int sid = (message >> 6) & 0x3f; // service identifier
|
||||||
|
int gap_no[4] = {3, 5, 8, 11};
|
||||||
|
qDebug() << "RDSParser::decode_type3: gap: " << gap_no[G] << " groups, SID: "
|
||||||
|
<< sid << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type3: message: " << message << " - aid: " << aid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type4(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
if (B)
|
||||||
|
{
|
||||||
|
qDebug() << "RDSParser::decode_type4: type 4B not implemented yet";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int hours = ((group[2] & 0x1) << 4) | ((group[3] >> 12) & 0x0f);
|
||||||
|
unsigned int minutes = (group[3] >> 6) & 0x3f;
|
||||||
|
double local_time_offset = .5 * (group[3] & 0x1f);
|
||||||
|
|
||||||
|
if ((group[3] >> 5) & 0x1) {
|
||||||
|
local_time_offset *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double modified_julian_date = ((group[1] & 0x03) << 15) | ((group[2] >> 1) & 0x7fff);
|
||||||
|
|
||||||
|
unsigned int year = int((modified_julian_date - 15078.2) / 365.25);
|
||||||
|
unsigned int month = int((modified_julian_date - 14956.1 - int(year * 365.25)) / 30.6001);
|
||||||
|
unsigned int day = modified_julian_date - 14956 - int(year * 365.25) - int(month * 30.6001);
|
||||||
|
bool K = ((month == 14) || (month == 15)) ? 1 : 0;
|
||||||
|
year += K;
|
||||||
|
month -= 1 + K * 12;
|
||||||
|
|
||||||
|
std::string time = str(boost::format("%02i.%02i.%4i, %02i:%02i (%+.1fh)")\
|
||||||
|
% day % month % (1900 + year) % hours % minutes % local_time_offset);
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type4: Clocktime: " << time.c_str();
|
||||||
|
|
||||||
|
//send_message(5,time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type5(unsigned int *group, bool B) {
|
||||||
|
qDebug() << "RDSParser::decode_type5: type5 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type6(unsigned int *group, bool B) {
|
||||||
|
qDebug() << "RDSParser::decode_type6: type 6 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type7(unsigned int *group, bool B) {
|
||||||
|
qDebug() << "RDSParser::decode_type7: type 7 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type8(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
if (B)
|
||||||
|
{
|
||||||
|
qDebug() << "RDSParser::decode_type8: type 8B not implemented yet";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool T = (group[1] >> 4) & 0x1; // 0 = user message, 1 = tuning info
|
||||||
|
bool F = (group[1] >> 3) & 0x1; // 0 = multi-group, 1 = single-group
|
||||||
|
bool D = (group[2] > 15) & 0x1; // 1 = diversion recommended
|
||||||
|
|
||||||
|
static unsigned long int free_format[4];
|
||||||
|
static int no_groups = 0;
|
||||||
|
|
||||||
|
if (T)
|
||||||
|
{ // tuning info
|
||||||
|
qDebug() << "RDSParser::decode_type8: #tuning info# ";
|
||||||
|
int variant = group[1] & 0xf;
|
||||||
|
|
||||||
|
if((variant > 3) && (variant < 10)) {
|
||||||
|
qDebug() << "RDSParser::decode_type8: variant: " << variant << " - "
|
||||||
|
<< group[2] << " " << group[3];
|
||||||
|
} else {
|
||||||
|
qDebug() << "RDSParser::decode_type8: invalid variant: " << variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (F || D)
|
||||||
|
{ // single-group or 1st of multi-group
|
||||||
|
unsigned int dp_ci = group[1] & 0x7; // duration & persistence or continuity index
|
||||||
|
bool sign = (group[2] >> 14) & 0x1; // event direction, 0 = +, 1 = -
|
||||||
|
unsigned int extent = (group[2] >> 11) & 0x7; // number of segments affected
|
||||||
|
unsigned int event = group[2] & 0x7ff; // event code, defined in ISO 14819-2
|
||||||
|
unsigned int location = group[3]; // location code, defined in ISO 14819-3
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type8: #user msg# " << (D ? "diversion recommended, " : "");
|
||||||
|
|
||||||
|
if (F) {
|
||||||
|
qDebug() << "RDSParser::decode_type8: single-grp, duration:" << (tmc_duration[dp_ci][0]).c_str();
|
||||||
|
} else {
|
||||||
|
qDebug() << "RDSParser::decode_type8: multi-grp, continuity index:" << dp_ci;
|
||||||
|
}
|
||||||
|
|
||||||
|
int event_line = RDSTMC::get_tmc_event_code_index(event, 1);
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type8: extent:" << (sign ? "-" : "") << extent + 1 << " segments"
|
||||||
|
<< ", event" << event << ":" << RDSTMC::get_tmc_events(event_line, 1).c_str()
|
||||||
|
<< ", location:" << location;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // 2nd or more of multi-group
|
||||||
|
unsigned int ci = group[1] & 0x7; // countinuity index
|
||||||
|
bool sg = (group[2] >> 14) & 0x1; // second group
|
||||||
|
unsigned int gsi = (group[2] >> 12) & 0x3; // group sequence
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type8: #user msg# multi-grp, continuity index:" << ci
|
||||||
|
<< (sg ? ", second group" : "") << ", gsi:" << gsi;
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_type8: free format: " << (group[2] & 0xfff) << " "
|
||||||
|
<< group[3];
|
||||||
|
// it's not clear if gsi=N-2 when gs=true
|
||||||
|
|
||||||
|
if (sg) {
|
||||||
|
no_groups = gsi;
|
||||||
|
}
|
||||||
|
|
||||||
|
free_format[gsi] = ((group[2] & 0xfff) << 12) | group[3];
|
||||||
|
|
||||||
|
if (gsi == 0) {
|
||||||
|
decode_optional_content(no_groups, free_format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_optional_content(int no_groups, unsigned long int *free_format)
|
||||||
|
{
|
||||||
|
int label = 0;
|
||||||
|
int content = 0;
|
||||||
|
int content_length = 0;
|
||||||
|
int ff_pointer = 0;
|
||||||
|
|
||||||
|
for (int i = no_groups; i == 0; i--)
|
||||||
|
{
|
||||||
|
ff_pointer = 12 + 16;
|
||||||
|
|
||||||
|
while(ff_pointer > 0)
|
||||||
|
{
|
||||||
|
ff_pointer -= 4;
|
||||||
|
label = (free_format[i] && (0xf << ff_pointer));
|
||||||
|
content_length = optional_content_lengths[label];
|
||||||
|
ff_pointer -= content_length;
|
||||||
|
content = (free_format[i] && (int(std::pow(2, content_length) - 1) << ff_pointer));
|
||||||
|
|
||||||
|
qDebug() << "RDSParser::decode_optional_content: TMC optional content (" << label_descriptions[label].c_str()
|
||||||
|
<< "):" << content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type9(unsigned int *group, bool B){
|
||||||
|
qDebug() << "RDSParser::decode_type9: type 9 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type10(unsigned int *group, bool B){
|
||||||
|
qDebug() << "RDSParser::decode_type10: type 10 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type11(unsigned int *group, bool B){
|
||||||
|
qDebug() << "RDSParser::decode_type11: type 11 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type12(unsigned int *group, bool B){
|
||||||
|
qDebug() << "RDSParser::decode_type12: type 12 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type13(unsigned int *group, bool B){
|
||||||
|
qDebug() << "RDSParser::decode_type13: type 13 not implemented yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type14(unsigned int *group, bool B)
|
||||||
|
{
|
||||||
|
bool tp_on = (group[1] >> 4) & 0x01;
|
||||||
|
char variant_code = group[1] & 0x0f;
|
||||||
|
unsigned int information = group[2];
|
||||||
|
unsigned int pi_on = group[3];
|
||||||
|
|
||||||
|
char pty_on = 0;
|
||||||
|
bool ta_on = 0;
|
||||||
|
static char ps_on[8] = {' ',' ',' ',' ',' ',' ',' ',' '};
|
||||||
|
double af_1 = 0;
|
||||||
|
double af_2 = 0;
|
||||||
|
|
||||||
|
if (!B)
|
||||||
|
{
|
||||||
|
switch (variant_code)
|
||||||
|
{
|
||||||
|
case 0: // PS(ON)
|
||||||
|
case 1: // PS(ON)
|
||||||
|
case 2: // PS(ON)
|
||||||
|
case 3: // PS(ON)
|
||||||
|
{
|
||||||
|
ps_on[variant_code * 2 ] = (information >> 8) & 0xff;
|
||||||
|
ps_on[variant_code * 2 + 1] = information & 0xff;
|
||||||
|
qDebug() << "RDSParser::decode_type14: PS(ON): \"" << std::string(ps_on, 8).c_str() << "\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: // AF
|
||||||
|
{
|
||||||
|
af_1 = 100.0 * (((information >> 8) & 0xff) + 875);
|
||||||
|
af_2 = 100.0 * ((information & 0xff) + 875);
|
||||||
|
std::string s = str(boost::format("AF:%3.2fMHz %3.2fMHz") % (af_1/1000) % (af_2/1000));
|
||||||
|
qDebug() << "RDSParser::decode_type14: " << s.c_str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: // mapped frequencies
|
||||||
|
case 6: // mapped frequencies
|
||||||
|
case 7: // mapped frequencies
|
||||||
|
case 8: // mapped frequencies
|
||||||
|
{
|
||||||
|
af_1 = 100.0 * (((information >> 8) & 0xff) + 875);
|
||||||
|
af_2 = 100.0 * ((information & 0xff) + 875);
|
||||||
|
std::string s = str(boost::format("TN:%3.2fMHz - ON:%3.2fMHz") % (af_1/1000) % (af_2/1000));
|
||||||
|
qDebug() << "RDSParser::decode_type14: " << s.c_str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 9: // mapped frequencies (AM)
|
||||||
|
{
|
||||||
|
af_1 = 100.0 * (((information >> 8) & 0xff) + 875);
|
||||||
|
af_2 = 9.0 * ((information & 0xff) - 16) + 531;
|
||||||
|
std::string s = str(boost::format("TN:%3.2fMHz - ON:%ikHz") % (af_1/1000) % int(af_2));
|
||||||
|
qDebug() << "RDSParser::decode_type14: " << s.c_str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 10: // unallocated
|
||||||
|
break;
|
||||||
|
case 11: // unallocated
|
||||||
|
break;
|
||||||
|
case 12: // linkage information
|
||||||
|
{
|
||||||
|
std::string s = str(boost::format("Linkage information: %x%x") % ((information >> 8) & 0xff) % (information & 0xff));
|
||||||
|
qDebug() << "RDSParser::decode_type14: " << s.c_str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 13: // PTY(ON), TA(ON)
|
||||||
|
{
|
||||||
|
ta_on = information & 0x01;
|
||||||
|
pty_on = (information >> 11) & 0x1f;
|
||||||
|
qDebug() << "RDSParser::decode_type14: PTY(ON):" << pty_table[int(pty_on)].c_str();
|
||||||
|
if(ta_on) {
|
||||||
|
qDebug() << "RDSParser::decode_type14: - TA";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 14: // PIN(ON)
|
||||||
|
{
|
||||||
|
std::string s = str(boost::format("PIN(ON):%x%x") % ((information >> 8) & 0xff) % (information & 0xff));
|
||||||
|
qDebug() << "RDSParser::decode_type14: " << s.c_str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 15: // Reserved for broadcasters use
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qDebug() << "RDSParser::decode_type14: invalid variant code:" << variant_code;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pi_on)
|
||||||
|
{
|
||||||
|
qDebug() << "RDSParser::decode_type14: PI(ON):" << pi_on;
|
||||||
|
|
||||||
|
if (tp_on) {
|
||||||
|
qDebug() << "RDSParser::decode_type14: TP(ON)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RDSParser::decode_type15(unsigned int *group, bool B){
|
||||||
|
qDebug() << "RDSParser::decode_type5: type 15 not implemented yet";
|
||||||
|
}
|
||||||
|
|
86
plugins/channel/bfm/rdsparser.h
Normal file
86
plugins/channel/bfm/rdsparser.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (C) 2015 F4EXB //
|
||||||
|
// written by Edouard Griffiths //
|
||||||
|
// //
|
||||||
|
// 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 as version 3 of the License, or //
|
||||||
|
// //
|
||||||
|
// 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 V3 for more details. //
|
||||||
|
// //
|
||||||
|
// You should have received a copy of the GNU General Public License //
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef PLUGINS_CHANNEL_BFM_RDSPARSER_H_
|
||||||
|
#define PLUGINS_CHANNEL_BFM_RDSPARSER_H_
|
||||||
|
|
||||||
|
class RDSParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RDSParser();
|
||||||
|
~RDSParser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a group retrieved by the decoder
|
||||||
|
*/
|
||||||
|
void parseGroup(unsigned int *group);
|
||||||
|
|
||||||
|
private:
|
||||||
|
double decode_af(unsigned int);
|
||||||
|
void decode_optional_content(int, unsigned long int *);
|
||||||
|
void decode_type0( unsigned int* group, bool B);
|
||||||
|
void decode_type1( unsigned int* group, bool B);
|
||||||
|
void decode_type2( unsigned int* group, bool B);
|
||||||
|
void decode_type3( unsigned int* group, bool B);
|
||||||
|
void decode_type4( unsigned int* group, bool B);
|
||||||
|
void decode_type5( unsigned int* group, bool B);
|
||||||
|
void decode_type6( unsigned int* group, bool B);
|
||||||
|
void decode_type7( unsigned int* group, bool B);
|
||||||
|
void decode_type8( unsigned int* group, bool B);
|
||||||
|
void decode_type9( unsigned int* group, bool B);
|
||||||
|
void decode_type10(unsigned int* group, bool B);
|
||||||
|
void decode_type11(unsigned int* group, bool B);
|
||||||
|
void decode_type12(unsigned int* group, bool B);
|
||||||
|
void decode_type13(unsigned int* group, bool B);
|
||||||
|
void decode_type14(unsigned int* group, bool B);
|
||||||
|
void decode_type15(unsigned int* group, bool B);
|
||||||
|
|
||||||
|
unsigned int program_identification;
|
||||||
|
unsigned char program_type;
|
||||||
|
unsigned char pi_country_identification;
|
||||||
|
unsigned char pi_area_coverage;
|
||||||
|
unsigned char pi_program_reference_number;
|
||||||
|
char radiotext[65+1];
|
||||||
|
char program_service_name[9];
|
||||||
|
bool radiotext_AB_flag;
|
||||||
|
bool traffic_program;
|
||||||
|
bool traffic_announcement;
|
||||||
|
bool music_speech;
|
||||||
|
bool mono_stereo;
|
||||||
|
bool artificial_head;
|
||||||
|
bool compressed;
|
||||||
|
bool static_pty;
|
||||||
|
bool debug;
|
||||||
|
bool log;
|
||||||
|
|
||||||
|
static const unsigned int offset_pos[5];
|
||||||
|
static const unsigned int offset_word[5];
|
||||||
|
static const unsigned int syndrome[5];
|
||||||
|
static const char * const offset_name[];
|
||||||
|
static const std::string pty_table[32];
|
||||||
|
static const std::string pi_country_codes[15][5];
|
||||||
|
static const std::string coverage_area_codes[16];
|
||||||
|
static const std::string rds_group_acronyms[16];
|
||||||
|
static const std::string language_codes[44];
|
||||||
|
static const std::string tmc_duration[8][2];
|
||||||
|
static const int optional_content_lengths[16];
|
||||||
|
static const std::string label_descriptions[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* PLUGINS_CHANNEL_BFM_RDSPARSER_H_ */
|
3707
plugins/channel/bfm/rdstmc.cpp
Normal file
3707
plugins/channel/bfm/rdstmc.cpp
Normal file
File diff suppressed because it is too large
Load Diff
30
plugins/channel/bfm/rdstmc.h
Normal file
30
plugins/channel/bfm/rdstmc.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (C) 2015 F4EXB //
|
||||||
|
// written by Edouard Griffiths //
|
||||||
|
// //
|
||||||
|
// 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 as version 3 of the License, or //
|
||||||
|
// //
|
||||||
|
// 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 V3 for more details. //
|
||||||
|
// //
|
||||||
|
// You should have received a copy of the GNU General Public License //
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef PLUGINS_CHANNEL_BFM_RDSTMC_H_
|
||||||
|
#define PLUGINS_CHANNEL_BFM_RDSTMC_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class RDSTMC
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::string get_tmc_events(unsigned int i, unsigned int j);
|
||||||
|
static int get_tmc_event_code_index(unsigned int i, unsigned int j);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* PLUGINS_CHANNEL_BFM_RDSTMC_H_ */
|
Loading…
Reference in New Issue
Block a user