Merge pull request #1307 from AlexandreRouma/new_rds

New rds demod and decode
This commit is contained in:
AlexandreRouma 2024-01-29 19:43:06 +01:00 committed by GitHub
commit 05ab17add3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1213 additions and 241 deletions

View File

@ -49,6 +49,7 @@ namespace dsp::demod {
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
alFir.init(NULL, audioFirTaps);
arFir.init(NULL, audioFirTaps);
xlator.init(NULL, -57000.0, samplerate);
rdsResamp.init(NULL, samplerate, 5000.0);
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
@ -56,9 +57,9 @@ namespace dsp::demod {
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
lprDelay.out.free();
lmrDelay.out.free();
arFir.out.free();
alFir.out.free();
xlator.out.free();
rdsResamp.out.free();
base_type::init(in);
@ -92,6 +93,7 @@ namespace dsp::demod {
alFir.setTaps(audioFirTaps);
arFir.setTaps(audioFirTaps);
xlator.setOffset(-57000.0, samplerate);
rdsResamp.setInSamplerate(samplerate);
reset();
@ -139,7 +141,7 @@ namespace dsp::demod {
base_type::tempStart();
}
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) {
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, complex_t* rdsout = NULL) {
// Demodulate
demod.process(count, in, demod.out.writeBuf);
if (_stereo) {
@ -152,24 +154,24 @@ namespace dsp::demod {
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, lmrDelay.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
// Do RDS demod
if (_rdsOut) {
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
// Translate to 0Hz
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// Resample to the output samplerate
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
}
// Convert output back to real for further processing
convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr);
convert::ComplexToReal::process(count, lmrDelay.out.writeBuf, lmr);
// Amplify by 2x
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
@ -193,24 +195,11 @@ namespace dsp::demod {
// Convert to complex
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
// Filter out pilot and run through PLL
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
// Translate to 0Hz
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
// Resample to the output samplerate
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
}
// Filter if needed
@ -240,7 +229,7 @@ namespace dsp::demod {
return count;
}
stream<float> rdsOut;
stream<complex_t> rdsOut;
protected:
double _deviation;
@ -253,13 +242,14 @@ namespace dsp::demod {
tap<complex_t> pilotFirTaps;
filter::FIR<complex_t, complex_t> pilotFir;
convert::RealToComplex rtoc;
channel::FrequencyXlator xlator;
loop::PLL pilotPLL;
math::Delay<float> lprDelay;
math::Delay<complex_t> lmrDelay;
tap<float> audioFirTaps;
filter::FIR<float, float> arFir;
filter::FIR<float, float> alFir;
multirate::RationalResampler<float> rdsResamp;
multirate::RationalResampler<dsp::complex_t> rdsResamp;
float* lmr;
float* l;

View File

@ -65,6 +65,11 @@ namespace dsp::loop {
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
inline void advancePhase() {
phase += freq;
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
T freq;
T phase;

View File

@ -7,6 +7,14 @@ namespace riff {
const char* LIST_SIGNATURE = "LIST";
const size_t RIFF_LABEL_SIZE = 4;
// Writer::Writer(const Writer&& b) {
// //file = std::move(b.file);
// }
Writer::~Writer() {
close();
}
bool Writer::open(std::string path, const char form[4]) {
std::lock_guard<std::recursive_mutex> lck(mtx);

View File

@ -20,6 +20,10 @@ namespace riff {
class Writer {
public:
Writer() {}
// Writer(const Writer&& b);
~Writer();
bool open(std::string path, const char form[4]);
bool isOpen();
void close();
@ -40,4 +44,23 @@ namespace riff {
std::ofstream file;
std::stack<ChunkDesc> chunks;
};
// class Reader {
// public:
// Reader();
// Reader(const Reader&& b);
// ~Reader();
// bool open(std::string path);
// bool isOpen();
// void close();
// const std::string& form();
// private:
// std::string _form;
// std::recursive_mutex mtx;
// std::ofstream file;
// };
}

View File

@ -0,0 +1,63 @@
#pragma once
#include <dsp/loop/pll.h>
#include "chrominance_filter.h"
// TODO: Should be 60 but had to try something
#define BURST_START (63+CHROMA_FIR_DELAY)
#define BURST_END (BURST_START+28)
#define A_PHASE ((135.0/180.0)*FL_M_PI)
#define B_PHASE ((-135.0/180.0)*FL_M_PI)
namespace dsp::loop {
class ChromaPLL : public PLL {
using base_type = PLL;
public:
ChromaPLL() {}
ChromaPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
}
inline int process(int count, complex_t* in, complex_t* out, bool aphase = false) {
// Process the pre-burst section
for (int i = 0; i < BURST_START; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
// Process the burst itself
if (aphase) {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - A_PHASE));
}
}
else {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - B_PHASE));
}
}
// Process the post-burst section
for (int i = BURST_END; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
inline int processBlank(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
};
}

View File

@ -0,0 +1,239 @@
#pragma once
#include <dsp/types.h>
inline const dsp::complex_t CHROMA_FIR[] = {
{-0.000005461290583903, -0.000011336784355655},
{ 0.000020060944485414, 0.000009851315045203},
{-0.000034177222729438, 0.000007245841504981},
{ 0.000027694034878705, -0.000033114740542635},
{-0.000001217597841648, 0.000039141482370942},
{-0.000008324593371228, -0.000011315001355976},
{-0.000038085228233509, -0.000010585909953738},
{ 0.000114833396071141, -0.000047778708840608},
{-0.000115428390169113, 0.000205816198882814},
{-0.000055467806072871, -0.000356692479491626},
{ 0.000349316846854190, 0.000326162940234916},
{-0.000558465829929114, -0.000048001521408724},
{ 0.000488176200631416, -0.000319593757302922},
{-0.000169437838021935, 0.000501610900725908},
{-0.000131793335799502, -0.000373003580727547},
{ 0.000166817395492786, 0.000105930895534474},
{ 0.000030499908326112, -0.000003048682668943},
{-0.000174999505027919, 0.000168008090089458},
{ 0.000054431163395030, -0.000385174790951272},
{ 0.000215876012859739, 0.000372695852521209},
{-0.000325534912280750, -0.000130173041693966},
{ 0.000154951430569290, -0.000045395998708328},
{ 0.000054324657659002, -0.000076028700470037},
{ 0.000015664427565764, 0.000348002612845696},
{-0.000345943017888332, -0.000402175417043307},
{ 0.000568731727879741, 0.000112347863435682},
{-0.000416485880859085, 0.000211750352828909},
{ 0.000087462353623011, -0.000188197153014309},
{-0.000032082305030264, -0.000136804226080664},
{ 0.000379089999045955, 0.000303466839685362},
{-0.000726760198519770, -0.000007022279302816},
{ 0.000619888661818195, -0.000476871323359809},
{-0.000151885493742993, 0.000595641190573181},
{-0.000100626407015494, -0.000227947144491108},
{-0.000201935458823941, -0.000107628631934340},
{ 0.000680260922139900, -0.000120771182888852},
{-0.000666108629277491, 0.000744775901128973},
{ 0.000067236591919755, -0.001044125966364420},
{ 0.000447037274751822, 0.000651912509450913},
{-0.000262675893448686, -0.000082499729563337},
{-0.000349821460486320, 0.000132102793530818},
{ 0.000507024815168287, -0.000837598610490618},
{ 0.000163814255478652, 0.001346530693477834},
{-0.000970457632383793, -0.000968411010101160},
{ 0.000974834882891140, 0.000116507082762032},
{-0.000225464280571542, 0.000137131865995708},
{-0.000211542240694642, 0.000563783548428947},
{-0.000414412310798766, -0.001309793399193736},
{ 0.001497010004594478, 0.001021907858926259},
{-0.001752019159639658, 0.000116536066154131},
{ 0.000872822027879430, -0.000783952720205569},
{-0.000032439446797970, 0.000184988059956734},
{ 0.000446259382722895, 0.000833040920509238},
{-0.001741577737284306, -0.000764423771425237},
{ 0.002306569133792772, -0.000593352416441601},
{-0.001336084746214192, 0.001744394557524181},
{-0.000015810020735495, -0.001342809547658260},
{ 0.000007636494885364, 0.000009498318627546},
{ 0.001403876768349702, 0.000326101441888391},
{-0.002351020828600226, 0.001098649819278302},
{ 0.001389314639579544, -0.002746943712072884},
{ 0.000526319899588909, 0.002635084366837732},
{-0.001109526585744687, -0.000950323796527721},
{-0.000307792427984886, -0.000013203419520794},
{ 0.001737955094951111, -0.001247368808692850},
{-0.000974502437588420, 0.003352512117661680},
{-0.001462571137390936, -0.003635296917435679},
{ 0.002783459090201693, 0.001604420226187745},
{-0.001471518558760170, 0.000211117948702137},
{-0.000575340825070194, 0.000601820846100026},
{ 0.000302090333345692, -0.003088058972305493},
{ 0.002496092353182990, 0.003912508340989065},
{-0.004645661091012423, -0.001630427298020200},
{ 0.003556824805628799, -0.001209822327859352},
{-0.000744999556260706, 0.001143238699138109},
{ 0.000144278726929409, 0.001638049051599065},
{-0.003025291044450178, -0.003226370992887968},
{ 0.006047866290490120, 0.000927406808799887},
{-0.005338456415106141, 0.003008811999350399},
{ 0.001642959659014839, -0.003972384205231079},
{ 0.000273874932822212, 0.000977326273749033},
{ 0.002315022846573390, 0.001695671268241410},
{-0.006240953957978884, 0.000207330368698293},
{ 0.006164252120861735, -0.005177351717451013},
{-0.001560310257561104, 0.007437030759707700},
{-0.002131333814462852, -0.004317129694157112},
{ 0.000280518918541908, 0.000134405998842553},
{ 0.004612116481180659, -0.001024468120657814},
{-0.005599300279638699, 0.006828277067771868},
{ 0.000228879728552504, -0.010675998154712657},
{ 0.005692081512980654, 0.007582243186569848},
{-0.005100500569859509, -0.001364751685737153},
{-0.000902490398043454, 0.000385770160220703},
{ 0.003673858819546609, -0.006701685283451640},
{ 0.002079056046131593, 0.012568579063417429},
{-0.010730008156911677, -0.009826454574016218},
{ 0.012092401380903161, 0.000921764172237851},
{-0.004714530989129091, 0.003151948807627123},
{-0.001055930168838909, 0.003228576712467020},
{-0.004343270165991213, -0.011924332879354394},
{ 0.016499994418955999, 0.010255324919126899},
{-0.021047239750251585, 0.002309419513135448},
{ 0.011855513874047341, -0.011604071033866310},
{-0.000777842281358575, 0.005916341648175263},
{ 0.004380939277688377, 0.007397670455730446},
{-0.021891594662401131, -0.008509480947490166},
{ 0.032787638290674201, -0.009950745850861956},
{-0.021022579272463194, 0.030030850567389102},
{-0.001508145650189953, -0.027571914870304640},
{ 0.004056649693022923, 0.004624901687718579},
{ 0.025728742586666287, 0.004824671348397606},
{-0.058002700931665603, 0.030198618296813803},
{ 0.043631619628438784, -0.096308304333327280},
{ 0.033451363423624300, 0.136687079396426990},
{-0.129387018420204200, -0.101540513046619400},
{ 0.172881344826560730, -0.000000000000005297},
{-0.129387018420198010, 0.101540513046627330},
{ 0.033451363423615862, -0.136687079396429050},
{ 0.043631619628444723, 0.096308304333324601},
{-0.058002700931667456, -0.030198618296810247},
{ 0.025728742586665992, -0.004824671348399184},
{ 0.004056649693022639, -0.004624901687718827},
{-0.001508145650188251, 0.027571914870304734},
{-0.021022579272465047, -0.030030850567387805},
{ 0.032787638290674812, 0.009950745850859947},
{-0.021891594662400610, 0.008509480947491507},
{ 0.004380939277687923, -0.007397670455730714},
{-0.000777842281358940, -0.005916341648175215},
{ 0.011855513874048058, 0.011604071033865578},
{-0.021047239750251731, -0.002309419513134139},
{ 0.016499994418955360, -0.010255324919127926},
{-0.004343270165990471, 0.011924332879354665},
{-0.001055930168839110, -0.003228576712466955},
{-0.004714530989129287, -0.003151948807626830},
{ 0.012092401380903103, -0.000921764172238603},
{-0.010730008156911072, 0.009826454574016881},
{ 0.002079056046130817, -0.012568579063417559},
{ 0.003673858819547020, 0.006701685283451416},
{-0.000902490398043478, -0.000385770160220647},
{-0.005100500569859424, 0.001364751685737466},
{ 0.005692081512980187, -0.007582243186570198},
{ 0.000228879728553163, 0.010675998154712643},
{-0.005599300279639117, -0.006828277067771524},
{ 0.004612116481180722, 0.001024468120657532},
{ 0.000280518918541900, -0.000134405998842571},
{-0.002131333814462586, 0.004317129694157243},
{-0.001560310257561563, -0.007437030759707604},
{ 0.006164252120862052, 0.005177351717450635},
{-0.006240953957978898, -0.000207330368697911},
{ 0.002315022846573286, -0.001695671268241552},
{ 0.000273874932822152, -0.000977326273749050},
{ 0.001642959659015084, 0.003972384205230976},
{-0.005338456415106324, -0.003008811999350072},
{ 0.006047866290490063, -0.000927406808800258},
{-0.003025291044449980, 0.003226370992888153},
{ 0.000144278726929308, -0.001638049051599074},
{-0.000744999556260777, -0.001143238699138063},
{ 0.003556824805628873, 0.001209822327859134},
{-0.004645661091012323, 0.001630427298020484},
{ 0.002496092353182751, -0.003912508340989219},
{ 0.000302090333345882, 0.003088058972305475},
{-0.000575340825070231, -0.000601820846099991},
{-0.001471518558760183, -0.000211117948702046},
{ 0.002783459090201593, -0.001604420226187919},
{-0.001462571137390710, 0.003635296917435769},
{-0.000974502437588628, -0.003352512117661619},
{ 0.001737955094951189, 0.001247368808692742},
{-0.000307792427984885, 0.000013203419520814},
{-0.001109526585744628, 0.000950323796527789},
{ 0.000526319899588746, -0.002635084366837765},
{ 0.001389314639579712, 0.002746943712072799},
{-0.002351020828600294, -0.001098649819278158},
{ 0.001403876768349682, -0.000326101441888477},
{ 0.000007636494885364, -0.000009498318627546},
{-0.000015810020735412, 0.001342809547658261},
{-0.001336084746214299, -0.001744394557524099},
{ 0.002306569133792808, 0.000593352416441460},
{-0.001741577737284259, 0.000764423771425344},
{ 0.000446259382722843, -0.000833040920509266},
{-0.000032439446797982, -0.000184988059956732},
{ 0.000872822027879478, 0.000783952720205515},
{-0.001752019159639665, -0.000116536066154024},
{ 0.001497010004594416, -0.001021907858926351},
{-0.000414412310798685, 0.001309793399193761},
{-0.000211542240694677, -0.000563783548428934},
{-0.000225464280571550, -0.000137131865995694},
{ 0.000974834882891133, -0.000116507082762092},
{-0.000970457632383734, 0.000968411010101219},
{ 0.000163814255478569, -0.001346530693477844},
{ 0.000507024815168339, 0.000837598610490586},
{-0.000349821460486328, -0.000132102793530797},
{-0.000262675893448681, 0.000082499729563353},
{ 0.000447037274751782, -0.000651912509450940},
{ 0.000067236591919819, 0.001044125966364416},
{-0.000666108629277537, -0.000744775901128932},
{ 0.000680260922139908, 0.000120771182888810},
{-0.000201935458823935, 0.000107628631934352},
{-0.000100626407015480, 0.000227947144491114},
{-0.000151885493743030, -0.000595641190573172},
{ 0.000619888661818225, 0.000476871323359771},
{-0.000726760198519770, 0.000007022279302861},
{ 0.000379089999045936, -0.000303466839685386},
{-0.000032082305030256, 0.000136804226080666},
{ 0.000087462353623023, 0.000188197153014303},
{-0.000416485880859098, -0.000211750352828883},
{ 0.000568731727879734, -0.000112347863435717},
{-0.000345943017888307, 0.000402175417043329},
{ 0.000015664427565742, -0.000348002612845697},
{ 0.000054324657659007, 0.000076028700470034},
{ 0.000154951430569292, 0.000045395998708319},
{-0.000325534912280742, 0.000130173041693986},
{ 0.000215876012859716, -0.000372695852521222},
{ 0.000054431163395054, 0.000385174790951269},
{-0.000174999505027930, -0.000168008090089447},
{ 0.000030499908326113, 0.000003048682668941},
{ 0.000166817395492779, -0.000105930895534485},
{-0.000131793335799479, 0.000373003580727555},
{-0.000169437838021966, -0.000501610900725898},
{ 0.000488176200631435, 0.000319593757302892},
{-0.000558465829929111, 0.000048001521408758},
{ 0.000349316846854170, -0.000326162940234938},
{-0.000055467806072849, 0.000356692479491629},
{-0.000115428390169126, -0.000205816198882806},
{ 0.000114833396071144, 0.000047778708840601},
{-0.000038085228233508, 0.000010585909953741},
{-0.000008324593371228, 0.000011315001355977},
{-0.000001217597841650, -0.000039141482370942},
{ 0.000027694034878707, 0.000033114740542633},
{-0.000034177222729439, -0.000007245841504979},
{ 0.000020060944485413, -0.000009851315045204},
{-0.000005461290583903, 0.000011336784355656},
};
#define CHROMA_FIR_SIZE (sizeof(CHROMA_FIR)/sizeof(dsp::complex_t))
#define CHROMA_FIR_DELAY ((CHROMA_FIR_SIZE-1)/2)

View File

@ -0,0 +1,193 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/phase_control_loop.h>
#include <dsp/taps/windowed_sinc.h>
#include <dsp/multirate/polyphase_bank.h>
#include <dsp/math/step.h>
class LineSync : public dsp::Processor<float, float> {
using base_type = dsp::Processor<float, float>;
public:
LineSync() {}
LineSync(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
~LineSync() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
}
void init(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
_omega = omega;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::init(in);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaGain = omegaGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muGain = muGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
}
void setSyncLevel(float level) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
syncLevel = level;
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Copy data to work buffer
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
if (test2) {
test2 = false;
offset += 5;
}
// Process all samples
while (offset < count) {
// Calculate new output value
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
float outVal;
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
base_type::out.writeBuf[outCount++] = outVal;
// If the end of the line is reached, process it and determin error
float error = 0;
if (outCount >= 720) {
// Compute averages.
float left = 0.0f, right = 0.0f;
for (int i = (720-17); i < 720; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 0; i < 27; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 27; i < (54+17); i++) {
right += base_type::out.writeBuf[i];
}
left *= (1.0f/44.0f);
right *= (1.0f/44.0f);
// If the sync is present, compute error
if ((left < syncLevel && right < syncLevel) && !forceLock) {
error = (left + syncBias - right);
locked = true;
}
else {
locked = false;
}
if (++counter >= 100) {
counter = 0;
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
}
// Output line
if (!base_type::out.swap(outCount)) { break; }
outCount = 0;
}
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
// Swap if some data was generated
base_type::_in->flush();
return outCount;
}
bool locked = false;
bool test2 = false;
float syncBias = 0.0f;
bool forceLock = false;
int counter = 0;
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)_interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
dsp::taps::free(lp);
}
dsp::multirate::PolyphaseBank<float> interpBank;
dsp::loop::PhaseControlLoop<double, false> pcl;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
int offset = 0;
int outCount = 0;
float* buffer;
float* bufStart;
float syncLevel = -0.03f;
};

View File

@ -10,6 +10,15 @@
#include <dsp/demod/quadrature.h>
#include <dsp/sink/handler_sink.h>
#include "linesync.h"
#include <dsp/loop/pll.h>
#include <dsp/convert/real_to_complex.h>
#include <dsp/filter/fir.h>
#include <dsp/taps/from_array.h>
#include "chrominance_filter.h"
#include "chroma_pll.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -17,7 +26,8 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
/* Description: */ "ATV decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1};
/* Max instances */ -1
};
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
@ -29,9 +39,16 @@ class ATVDecoderModule : public ModuleManager::Instance {
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
sink.init(&demod.out, handler, this);
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
sink.init(&sync.out, handler, this);
r2c.init(NULL);
chromaTaps = dsp::taps::fromArray(CHROMA_FIR_SIZE, CHROMA_FIR);
fir.init(NULL, chromaTaps);
pll.init(NULL, 0.01, 0.0, dsp::math::hzToRads(4433618.75, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*0.90, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*1.1, SAMPLE_RATE));
demod.start();
sync.start();
sink.start();
gui::menu.registerEntry(name, menuHandler, this, this);
@ -47,9 +64,13 @@ class ATVDecoderModule : public ModuleManager::Instance {
void postInit() {}
void enable() { enabled = true; }
void enable() {
enabled = true;
}
void disable() { enabled = false; }
void disable() {
enabled = false;
}
bool isEnabled() { return enabled; }
@ -61,6 +82,8 @@ class ATVDecoderModule : public ModuleManager::Instance {
style::beginDisabled();
}
// Ideal width for testing: 750pixels
ImGui::FillWidth();
_this->img.draw();
@ -76,6 +99,28 @@ class ATVDecoderModule : public ModuleManager::Instance {
ImGui::FillWidth();
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
ImGui::LeftLabel("Sync Bias");
ImGui::FillWidth();
ImGui::SliderFloat("##syncBias", &_this->sync.syncBias,-0.1, 0.1);
if (ImGui::Button("Test2")) {
_this->sync.test2 = true;
}
if (ImGui::Button("Switch frame")) {
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
if (_this->sync.locked) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::Checkbox("Force Lock", &_this->sync.forceLock);
if (!_this->enabled) {
style::endDisabled();
}
@ -84,70 +129,66 @@ class ATVDecoderModule : public ModuleManager::Instance {
static void handler(float *data, int count, void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
uint8_t *buf = (uint8_t *)_this->img.buffer;
float val;
float imval;
int pos = 0;
// Convert line to complex
_this->r2c.process(720, data, _this->r2c.out.writeBuf);
// Isolate the chroma subcarrier
_this->fir.process(720, _this->r2c.out.writeBuf, _this->fir.out.writeBuf);
// Run chroma carrier through the PLL
_this->pll.process(720, _this->fir.out.writeBuf, _this->pll.out.writeBuf, ((_this->ypos%2)==1) ^ _this->evenFrame);
// Render line to the image without color
//int lypos = _this->ypos - 1;
//if (lypos < 0) { lypos = 624; }
//uint32_t* lastLine = &((uint32_t *)_this->img.buffer)[(lypos < 313) ? (lypos*720*2) : ((((lypos - 313)*2)+1)*720) ];
//uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos < 313) ? (_this->ypos*720*2) : ((((_this->ypos - 313)*2)+1)*720) ];
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[_this->ypos*720];
for (int i = 0; i < count; i++) {
val = data[i];
// Sync
if (val < _this->sync_level) {
_this->sync_count++;
}
else {
if (_this->sync_count >= 300) {
_this->short_sync = 0;
}
else if (_this->sync_count >= 33) {
if (_this->short_sync == 5) {
_this->even_field = false;
_this->ypos = 0;
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
else if (_this->short_sync == 4) {
_this->even_field = true;
_this->ypos = 0;
}
_this->xpos = 0;
_this->short_sync = 0;
}
else if (_this->sync_count >= 15) {
_this->short_sync++;
}
_this->sync_count = 0;
}
//float imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
currentLine[i] = 0xFF000000 | (im << 8) | re;
}
// Vertical scan logic
_this->ypos++;
bool rollover = _this->ypos >= 625;
if (rollover) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
// Draw
imval = std::clamp<float>((val - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
if (_this->even_field) {
pos = ((720 * _this->ypos * 2) + _this->xpos) * 4;
}
else {
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4;
}
// Measure vsync levels
float sync0 = 0.0f, sync1 = 0.0f;
for (int i = 0; i < 306; i++) {
sync0 += data[i];
}
for (int i = (720/2); i < ((720/2)+306); i++) {
sync1 += data[i];
}
sync0 *= (1.0f/305.0f);
sync1 *= (1.0f/305.0f);
buf[pos] = imval;
buf[pos + 1] = imval;
buf[pos + 2] = imval;
buf[pos + 3] = imval;
// Save sync detection to history
_this->syncHistory >>= 2;
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
// Image logic
_this->xpos++;
if (_this->xpos >= 720) {
_this->ypos++;
_this->xpos = 0;
}
if (_this->ypos >= 312) {
_this->ypos = 0;
_this->xpos = 0;
_this->even_field = !_this->even_field;
if (_this->even_field) {
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
// Trigger vsync in case one is detected
// TODO: Also sync with odd field
if (!rollover && _this->syncHistory == 0b0000011111) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
}
@ -156,19 +197,27 @@ class ATVDecoderModule : public ModuleManager::Instance {
VFOManager::VFO *vfo = NULL;
dsp::demod::Quadrature demod;
LineSync sync;
dsp::sink::Handler<float> sink;
int xpos = 0;
dsp::convert::RealToComplex r2c;
dsp::tap<dsp::complex_t> chromaTaps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::ChromaPLL pll;
int ypos = 0;
bool even_field = false;
float sync_level = -0.3f;
bool evenFrame = false;
std::mutex evenFrameMtx;
float sync_level = -0.06f;
int sync_count = 0;
int short_sync = 0;
float minLvl = 0.0f;
float spanLvl = 1.0f;
bool lockedLines = 0;
uint16_t syncHistory = 0;
ImGui::ImageDisplay img;
};

View File

@ -1,12 +1,7 @@
#pragma once
#include "../demod.h"
#include <dsp/demod/broadcast_fm.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/clock_recovery/fd.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/manchester_decoder.h>
#include <dsp/digital/differential_decoder.h>
#include "../rds_demod.h"
#include <gui/widgets/symbol_diagram.h>
#include <fstream>
#include <rds.h>
@ -14,9 +9,9 @@
namespace demod {
class WFM : public Demodulator {
public:
WFM() {}
WFM() : diag(0.5, 4096) {}
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
init(name, config, input, bandwidth, audioSR);
}
@ -45,33 +40,37 @@ namespace demod {
if (config->conf[name][getName()].contains("rds")) {
_rds = config->conf[name][getName()]["rds"];
}
if (config->conf[name][getName()].contains("rdsInfo")) {
_rdsInfo = config->conf[name][getName()]["rdsInfo"];
}
_config->release(modified);
// Define structure
// Init DSP
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
slice.init(&recov.out);
manch.init(&slice.out);
diff.init(&manch.out, 2);
hs.init(&diff.out, rdsHandler, this);
rdsDemod.init(&demod.rdsOut, _rdsInfo);
hs.init(&rdsDemod.out, rdsHandler, this);
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
diagHandler.init(&reshape.out, _diagHandler, this);
// Init RDS display
diag.lines.push_back(-0.8);
diag.lines.push_back(0.8);
}
void start() {
demod.start();
recov.start();
slice.start();
manch.start();
diff.start();
rdsDemod.start();
hs.start();
reshape.start();
diagHandler.start();
}
void stop() {
demod.stop();
recov.stop();
slice.stop();
manch.stop();
diff.stop();
rdsDemod.stop();
hs.stop();
reshape.stop();
diagHandler.stop();
}
void showMenu() {
@ -94,14 +93,106 @@ namespace demod {
_config->release(true);
}
// if (_rds) {
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
// }
// TODO: This will break when the entire radio module is disabled
if (!_rds) { ImGui::BeginDisabled(); }
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
setAdvancedRds(_rdsInfo);
_config->acquire();
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
_config->release(true);
}
if (!_rds) { ImGui::EndDisabled(); }
float menuWidth = ImGui::GetContentRegionAvail().x;
if (_rds && _rdsInfo) {
ImGui::BeginTable(("##radio_wfm_rds_info_tbl_" + name).c_str(), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
if (rdsDecode.piCodeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
ImGui::Text("0x%04X (%s)", rdsDecode.getPICode(), rdsDecode.getCallsign().c_str());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getCountryCode());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (%d)", rds::AREA_COVERAGE_TO_STR[rdsDecode.getProgramCoverage()], rdsDecode.getProgramCoverage());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getProgramRefNumber());
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("0x---- (----)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--"); // TODO: String
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--");
}
if (rdsDecode.programTypeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_US_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)"); // TODO: String
}
if (rdsDecode.musicValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s", rdsDecode.getMusic() ? "Yes":"No");
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("---");
}
ImGui::EndTable();
ImGui::SetNextItemWidth(menuWidth);
diag.draw();
}
}
void setBandwidth(double bandwidth) {
@ -139,12 +230,24 @@ namespace demod {
demod.setStereo(_stereo);
}
void setAdvancedRds(bool enabled) {
rdsDemod.setSoftEnabled(enabled);
_rdsInfo = enabled;
}
private:
static void rdsHandler(uint8_t* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
_this->rdsDecode.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
WFM* _this = (WFM*)ctx;
if (!_this->_rds) { return; }
@ -186,20 +289,22 @@ namespace demod {
}
dsp::demod::BroadcastFM demod;
dsp::clock_recovery::FD recov;
dsp::digital::BinarySlicer slice;
dsp::digital::ManchesterDecoder manch;
dsp::digital::DifferentialDecoder diff;
RDSDemod rdsDemod;
dsp::sink::Handler<uint8_t> hs;
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
rds::RDSDecoder rdsDecode;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<float> diagHandler;
ImGui::SymbolDiagram diag;
rds::Decoder rdsDecode;
ConfigManager* _config = NULL;
bool _stereo = false;
bool _lowPass = true;
bool _rds = false;
bool _rdsInfo = false;
float muGain = 0.01;
float omegaGain = (0.01*0.01)/4.0;

View File

@ -3,6 +3,8 @@
#include <map>
#include <algorithm>
#include <utils/flog.h>
namespace rds {
std::map<uint16_t, BlockType> SYNDROMES = {
{ 0b1111011000, BLOCK_TYPE_A },
@ -28,7 +30,7 @@ namespace rds {
const int DATA_LEN = 16;
const int POLY_LEN = 10;
void RDSDecoder::process(uint8_t* symbols, int count) {
void Decoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) {
// Shift in the bit
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
@ -54,18 +56,26 @@ namespace rds {
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT);
}
// Save block while correcting errors (NOT YET)
// Save block while correcting errors (NOT YET) <- idk why the "not yet is here", TODO: find why
blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
// Update continous group count
if (type == BLOCK_TYPE_A) { contGroup = 1; }
else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; }
// If block type is A, decode it directly, otherwise, update continous count
if (type == BLOCK_TYPE_A) {
decodeBlockA();
}
else if (type == BLOCK_TYPE_B) { contGroup = 1; }
else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; }
else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; }
else { contGroup = 0; }
else {
// If block B is available, decode it alone.
if (contGroup == 1) {
decodeBlockB();
}
contGroup = 0;
}
// If we've got an entire group, process it
if (contGroup >= 4) {
if (contGroup >= 3) {
contGroup = 0;
decodeGroup();
}
@ -76,7 +86,7 @@ namespace rds {
}
}
uint16_t RDSDecoder::calcSyndrome(uint32_t block) {
uint16_t Decoder::calcSyndrome(uint32_t block) {
uint16_t syn = 0;
// Calculate the syndrome using a LFSR
@ -95,7 +105,7 @@ namespace rds {
return syn;
}
uint32_t RDSDecoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
uint32_t Decoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
// Subtract the offset from block
block ^= (uint32_t)OFFSETS[type];
uint32_t out = block;
@ -124,96 +134,168 @@ namespace rds {
return out;
}
void RDSDecoder::decodeGroup() {
std::lock_guard<std::mutex> lck(groupMtx);
auto now = std::chrono::high_resolution_clock::now();
anyGroupLastUpdate = now;
void Decoder::decodeBlockA() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockAMtx);
// Make sure blocks A and B are available
if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; }
// If it didn't decode properly return
if (!blockAvail[BLOCK_TYPE_A]) { return; }
// Decode PI code
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF;
decodeCallsign();
// Update timeout
blockALastUpdate = std::chrono::high_resolution_clock::now();;
}
void Decoder::decodeBlockB() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockBMtx);
// If it didn't decode properly return
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode group type and version
uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
// Decode traffic program and program type
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
if (groupType == 0) {
group0LastUpdate = now;
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Update timeout
blockBLastUpdate = std::chrono::high_resolution_clock::now();
}
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
void Decoder::decodeGroup0() {
// Acquire lock
std::lock_guard<std::mutex> lck(group0Mtx);
// Decode Block B data
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Decode Block C data
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
// Update timeout
group0LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup2() {
// Acquire lock
std::lock_guard<std::mutex> lck(group2Mtx);
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
}
rtAB = nAB;
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else {
uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else if (groupType == 2) {
group2LastUpdate = now;
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
}
rtAB = nAB;
// Update timeout
group2LastUpdate = std::chrono::high_resolution_clock::now();
}
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else {
uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
void Decoder::decodeGroup() {
// Make sure blocks B is available
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode block B
decodeBlockB();
// Decode depending on group type
switch (groupType) {
case 0:
decodeGroup0();
break;
case 2:
decodeGroup2();
break;
default:
break;
}
}
bool RDSDecoder::anyGroupValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0;
void Decoder::decodeCallsign() {
// Determin first better based on offset
bool w = (piCode >= 21672);
callsign = w ? 'W' : 'K';
// Base25 decode the rest
std::string restStr;
int rest = piCode - (w ? 21672 : 4096);
while (rest) {
restStr += 'A' + (rest % 26);
rest /= 26;
}
// Reorder chars
for (int i = restStr.size() - 1; i >= 0; i--) {
callsign += restStr[i];
}
}
bool RDSDecoder::group0Valid() {
bool Decoder::blockAValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockALastUpdate)).count() < RDS_BLOCK_A_TIMEOUT_MS;
}
bool RDSDecoder::group2Valid() {
bool Decoder::blockBValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockBLastUpdate)).count() < RDS_BLOCK_B_TIMEOUT_MS;
}
bool Decoder::group0Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < RDS_GROUP_0_TIMEOUT_MS;
}
bool Decoder::group2Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < RDS_GROUP_2_TIMEOUT_MS;
}
}

View File

@ -4,6 +4,11 @@
#include <chrono>
#include <mutex>
#define RDS_BLOCK_A_TIMEOUT_MS 5000.0
#define RDS_BLOCK_B_TIMEOUT_MS 5000.0
#define RDS_GROUP_0_TIMEOUT_MS 5000.0
#define RDS_GROUP_2_TIMEOUT_MS 5000.0
namespace rds {
enum BlockType {
BLOCK_TYPE_A,
@ -20,22 +25,42 @@ namespace rds {
};
enum AreaCoverage {
AREA_COVERAGE_LOCAL,
AREA_COVERAGE_INTERNATIONAL,
AREA_COVERAGE_NATIONAL,
AREA_COVERAGE_SUPRA_NATIONAL,
AREA_COVERAGE_REGIONAL1,
AREA_COVERAGE_REGIONAL2,
AREA_COVERAGE_REGIONAL3,
AREA_COVERAGE_REGIONAL4,
AREA_COVERAGE_REGIONAL5,
AREA_COVERAGE_REGIONAL6,
AREA_COVERAGE_REGIONAL7,
AREA_COVERAGE_REGIONAL8,
AREA_COVERAGE_REGIONAL9,
AREA_COVERAGE_REGIONAL10,
AREA_COVERAGE_REGIONAL11,
AREA_COVERAGE_REGIONAL12
AREA_COVERAGE_INVALID = -1,
AREA_COVERAGE_LOCAL = 0,
AREA_COVERAGE_INTERNATIONAL = 1,
AREA_COVERAGE_NATIONAL = 2,
AREA_COVERAGE_SUPRA_NATIONAL = 3,
AREA_COVERAGE_REGIONAL1 = 4,
AREA_COVERAGE_REGIONAL2 = 5,
AREA_COVERAGE_REGIONAL3 = 6,
AREA_COVERAGE_REGIONAL4 = 7,
AREA_COVERAGE_REGIONAL5 = 8,
AREA_COVERAGE_REGIONAL6 = 9,
AREA_COVERAGE_REGIONAL7 = 10,
AREA_COVERAGE_REGIONAL8 = 11,
AREA_COVERAGE_REGIONAL9 = 12,
AREA_COVERAGE_REGIONAL10 = 13,
AREA_COVERAGE_REGIONAL11 = 14,
AREA_COVERAGE_REGIONAL12 = 15
};
inline const char* AREA_COVERAGE_TO_STR[] = {
"Local",
"International",
"National",
"Supra-National",
"Regional 1",
"Regional 2",
"Regional 3",
"Regional 4",
"Regional 5",
"Regional 6",
"Regional 7",
"Regional 8",
"Regional 9",
"Regional 10",
"Regional 11",
"Regional 12",
};
enum ProgramType {
@ -108,6 +133,76 @@ namespace rds {
PROGRAM_TYPE_EU_ALARM = 31
};
inline const char* PROGRAM_TYPE_EU_TO_STR[] = {
"None",
"News",
"Current Affairs",
"Information",
"Sports",
"Education",
"Drama",
"Culture",
"Science",
"Varied",
"Pop Music",
"Rock Music",
"Easy Listening Music",
"Light Classical",
"Serious Classical",
"Other Music",
"Weather",
"Finance",
"Children Program",
"Social Affairs",
"Religion",
"Phone-in",
"Travel",
"Leisure",
"Jazz Music",
"Country Music",
"National Music",
"Oldies Music",
"Folk Music",
"Documentary",
"Alarm Test",
"Alarm",
};
inline const char* PROGRAM_TYPE_US_TO_STR[] = {
"None",
"News",
"Information",
"Sports",
"Talk",
"Rock",
"Classic Rock",
"Adult Hits",
"Soft Rock",
"Top 40",
"Country",
"Oldies",
"Soft",
"Nostalgia",
"Jazz",
"Classical",
"Rythm and Blues",
"Soft Rythm and Blues",
"Foreign Language",
"Religious Music",
"Religious Talk",
"Personality",
"Public",
"College",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Weather",
"Emergency Test",
"Emergency",
};
enum DecoderIdentification {
DECODER_IDENT_STEREO = (1 << 0),
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
@ -115,33 +210,41 @@ namespace rds {
DECODER_IDENT_VARIABLE_PTY = (1 << 0)
};
class RDSDecoder {
class Decoder {
public:
void process(uint8_t* symbols, int count);
bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; }
bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; }
bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); }
uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; }
std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; }
bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(blockBMtx); return blockBValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(blockBMtx); return programType; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; }
bool musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(group0Mtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(group0Mtx); return programServiceName; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(group2Mtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(group2Mtx); return radioText; }
private:
static uint16_t calcSyndrome(uint32_t block);
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
void decodeBlockA();
void decodeBlockB();
void decodeGroup0();
void decodeGroup2();
void decodeGroup();
bool anyGroupValid();
void decodeCallsign();
bool blockAValid();
bool blockBValid();
bool group0Valid();
bool group2Valid();
@ -154,17 +257,26 @@ namespace rds {
uint32_t blocks[_BLOCK_TYPE_COUNT];
bool blockAvail[_BLOCK_TYPE_COUNT];
// All groups
std::mutex groupMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate;
// Block A (All groups)
std::mutex blockAMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01
uint16_t piCode;
uint8_t countryCode;
AreaCoverage programCoverage;
uint8_t programRefNumber;
std::string callsign;
// Block B (All groups)
std::mutex blockBMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockBLastUpdate{}; // 1970-01-01
uint8_t groupType;
GroupVersion groupVer;
bool trafficProgram;
ProgramType programType;
// Group type 0
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate;
std::mutex group0Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate{}; // 1970-01-01
bool trafficAnnouncement;
bool music;
uint8_t decoderIdent;
@ -172,7 +284,8 @@ namespace rds {
std::string programServiceName = " ";
// Group type 2
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate;
std::mutex group2Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate{}; // 1970-01-01
bool rtAB = false;
std::string radioText = " ";

View File

@ -0,0 +1,102 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/loop/costas.h>
#include <dsp/taps/band_pass.h>
#include <dsp/filter/fir.h>
#include <dsp/convert/complex_to_real.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/differential_decoder.h>
class RDSDemod : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
RDSDemod() {}
RDSDemod(dsp::stream<dsp::complex_t>* in, bool enableSoft) { init(in, enableSoft); }
~RDSDemod() {}
void init(dsp::stream<dsp::complex_t>* in, bool enableSoft) {
// Save config
this->enableSoft = enableSoft;
// Initialize the DSP
agc.init(NULL, 1.0, 1e6, 0.1);
costas.init(NULL, 0.005f);
taps = dsp::taps::bandPass<dsp::complex_t>(0, 2375, 100, 5000);
fir.init(NULL, taps);
double baudfreq = dsp::math::hzToRads(2375.0/2.0, 5000);
costas2.init(NULL, 0.01, 0.0, baudfreq, baudfreq - (baudfreq*0.1), baudfreq + (baudfreq*0.1));
recov.init(NULL, 5000.0 / (2375.0 / 2.0), 1e-6, 0.01, 0.01);
diff.init(NULL, 2);
// Free useless buffers
agc.out.free();
fir.out.free();
costas2.out.free();
recov.out.free();
// Init the rest
base_type::init(in);
}
void setSoftEnabled(bool enable) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
enableSoft = enable;
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
agc.reset();
costas.reset();
fir.reset();
costas2.reset();
recov.reset();
diff.reset();
base_type::tempStart();
}
inline int process(int count, dsp::complex_t* in, float* softOut, uint8_t* hardOut) {
count = agc.process(count, in, costas.out.readBuf);
count = costas.process(count, costas.out.readBuf, costas.out.writeBuf);
count = fir.process(count, costas.out.writeBuf, costas.out.writeBuf);
count = costas2.process(count, costas.out.writeBuf, costas.out.readBuf);
count = dsp::convert::ComplexToReal::process(count, costas.out.readBuf, softOut);
count = recov.process(count, softOut, softOut);
count = dsp::digital::BinarySlicer::process(count, softOut, diff.out.readBuf);
count = diff.process(count, diff.out.readBuf, hardOut);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (enableSoft) {
if (!soft.swap(count)) { return -1; }
}
return count;
}
dsp::stream<float> soft;
private:
bool enableSoft = false;
dsp::loop::FastAGC<dsp::complex_t> agc;
dsp::loop::Costas<2> costas;
dsp::tap<dsp::complex_t> taps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::Costas<2> costas2;
dsp::clock_recovery::MM<float> recov;
dsp::digital::DifferentialDecoder diff;
};

View File

@ -347,7 +347,7 @@ private:
static void start(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
if (_this->running) { return; }
if (_this->devCount == 0) { return; }
if (_this->devCount <= 0) { return; }
// Open device
bladerf_devinfo info = _this->devInfoList[_this->devId];