1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-18 22:31:48 -05:00

BFM demod: working RDS support

This commit is contained in:
f4exb 2015-12-13 09:45:29 +01:00
parent 6782c753ad
commit 48855bfb63
13 changed files with 4719 additions and 14 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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()));

View File

@ -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;

View File

@ -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 /////////////////////////

View File

@ -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);

View File

@ -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

View File

@ -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;
}; };

View 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",
"Childrens 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";
}

View 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_ */

File diff suppressed because it is too large Load Diff

View 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_ */