aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ConfigParser.cpp88
-rw-r--r--src/DabMux.cpp2
-rw-r--r--src/Makefile.am1
-rw-r--r--src/MuxElements.cpp56
-rw-r--r--src/MuxElements.h55
-rw-r--r--src/RemoteControl.h3
-rw-r--r--src/fig/FIG0.h1
-rw-r--r--src/fig/FIG0_6.cpp353
-rw-r--r--src/fig/FIG0_6.h98
-rw-r--r--src/fig/FIG0structs.h26
-rw-r--r--src/fig/FIGCarousel.cpp4
-rw-r--r--src/fig/FIGCarousel.h2
-rw-r--r--src/utils.cpp39
13 files changed, 725 insertions, 3 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
index 0f05076..1b6093e 100644
--- a/src/ConfigParser.cpp
+++ b/src/ConfigParser.cpp
@@ -116,6 +116,92 @@ static uint16_t get_announcement_flag_from_ptree(
return flags;
}
+// Parse the linkage section
+static void parse_linkage(boost::property_tree::ptree& pt,
+ std::shared_ptr<dabEnsemble> ensemble)
+{
+ using boost::property_tree::ptree;
+ using boost::property_tree::ptree_error;
+
+ auto pt_linking = pt.get_child_optional("linking");
+ if (pt_linking)
+ {
+ for (const auto& it : *pt_linking) {
+ const string setuid = it.first;
+ const ptree pt_set = it.second;
+
+ uint16_t lsn = hexparse(pt_set.get("lsn", "0"));
+ if (lsn == 0) {
+ etiLog.level(error) << "LSN for linking set " <<
+ setuid << " invalid or missing";
+ throw runtime_error("Invalid service linking definition");
+ }
+
+ bool active = pt_set.get("active", false);
+ bool hard = pt_set.get("hard", true);
+ bool international = pt_set.get("international", false);
+
+ string service_uid = pt_set.get("keyservice", "");
+ if (service_uid.empty()) {
+ etiLog.level(error) << "Key Service for linking set " <<
+ setuid << " invalid or missing";
+ throw runtime_error("Invalid service linking definition");
+ }
+
+ auto linkageset = make_shared<LinkageSet>(setuid, lsn, hard, international);
+ linkageset->data.active = active;
+ linkageset->data.keyservice = service_uid; // TODO check if it exists
+
+ auto pt_list = pt_set.get_child_optional("list");
+ if (not pt_list) {
+ etiLog.level(error) << "list missing in linking set " <<
+ setuid;
+ throw runtime_error("Invalid service linking definition");
+ }
+
+ for (const auto& it : *pt_list) {
+ const string linkuid = it.first;
+ const ptree pt_link = it.second;
+
+ ServiceLink link;
+
+ string link_type = pt_link.get("type", "");
+ if (link_type == "dab") link.type = ServiceLinkType::DAB;
+ else if (link_type == "fm") link.type = ServiceLinkType::FM;
+ else if (link_type == "drm") link.type = ServiceLinkType::DRM;
+ else if (link_type == "amss") link.type = ServiceLinkType::AMSS;
+ else {
+ etiLog.level(error) << "Invalid type " << link_type <<
+ " for link " << linkuid;
+ throw runtime_error("Invalid service linking definition");
+ }
+
+ link.id = hexparse(pt_link.get("id", "0"));
+ if (link.id == 0) {
+ etiLog.level(error) << "id for link " <<
+ linkuid << " invalid or missing";
+ throw runtime_error("Invalid service linking definition");
+ }
+
+ if (international) {
+ link.ecc = hexparse(pt_link.get("ecc", "0"));
+ if (link.ecc == 0) {
+ etiLog.level(error) << "ecc for link " <<
+ linkuid << " invalid or missing";
+ throw runtime_error("Invalid service linking definition");
+ }
+ }
+ else {
+ link.ecc = 0;
+ }
+
+ linkageset->data.id_list.push_back(link);
+ }
+ ensemble->linkagesets.push_back(linkageset);
+ }
+ }
+}
+
void parse_ptree(
boost::property_tree::ptree& pt,
std::shared_ptr<dabEnsemble> ensemble)
@@ -528,6 +614,8 @@ void parse_ptree(
ensemble->components.push_back(component);
}
+
+ parse_linkage(pt, ensemble);
}
static Inputs::dab_input_zmq_config_t setup_zmq_input(
diff --git a/src/DabMux.cpp b/src/DabMux.cpp
index 3927420..04d1980 100644
--- a/src/DabMux.cpp
+++ b/src/DabMux.cpp
@@ -7,7 +7,7 @@
Matthias P. Braendli, matthias.braendli@mpb.li
http://www.opendigitalradio.org
- */
+ */
/*
This file is part of ODR-DabMux.
diff --git a/src/Makefile.am b/src/Makefile.am
index 17e68fe..de2aa37 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -86,6 +86,7 @@ odr_dabmux_SOURCES =DabMux.cpp DabMux.h \
fig/FIG0_2.cpp fig/FIG0_2.h \
fig/FIG0_3.cpp fig/FIG0_3.h \
fig/FIG0_5.cpp fig/FIG0_5.h \
+ fig/FIG0_6.cpp fig/FIG0_6.h \
fig/FIG0_8.cpp fig/FIG0_8.h \
fig/FIG0_9.cpp fig/FIG0_9.h \
fig/FIG0_10.cpp fig/FIG0_10.h \
diff --git a/src/MuxElements.cpp b/src/MuxElements.cpp
index 79aabf6..7f92652 100644
--- a/src/MuxElements.cpp
+++ b/src/MuxElements.cpp
@@ -662,4 +662,60 @@ unsigned short DabSubchannel::getSizeDWord() const
return (bitrate * 3) >> 3;
}
+LinkageSet::LinkageSet(string name, uint16_t lsn, bool hard, bool international) :
+ RemoteControllable(name)
+{
+ data.lsn = lsn;
+ data.active = false;
+ data.hard = hard;
+ data.international = international;
+ RC_ADD_PARAMETER(active, "Activate this linkage set [0 or 1]");
+}
+
+void LinkageSet::set_parameter(const string& parameter, const string& value)
+{
+ if (parameter == "active") {
+ stringstream ss;
+ ss << value;
+ ss >> data.active;
+ }
+ else {
+ stringstream ss;
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+}
+
+const string LinkageSet::get_parameter(const string& parameter) const
+{
+ stringstream ss;
+ if (parameter == "active") {
+ ss << data.active;
+ }
+ else {
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+ return ss.str();
+}
+
+LinkageSetData LinkageSetData::filter_type(ServiceLinkType type)
+{
+ LinkageSetData lsd;
+
+ lsd.lsn = lsn;
+ lsd.active = active;
+ lsd.hard = hard;
+ lsd.international = international;
+ lsd.keyservice = keyservice;
+ for (const auto& link : id_list) {
+ if (link.type == type) {
+ lsd.id_list.push_back(link);
+ }
+ }
+
+ return lsd;
+}
diff --git a/src/MuxElements.h b/src/MuxElements.h
index 7324cdc..7ff23a6 100644
--- a/src/MuxElements.h
+++ b/src/MuxElements.h
@@ -191,8 +191,9 @@ class DabLabel
class DabService;
class DabComponent;
-
class DabSubchannel;
+class LinkageSet;
+
class dabEnsemble : public RemoteControllable {
public:
dabEnsemble()
@@ -228,6 +229,7 @@ class dabEnsemble : public RemoteControllable {
std::vector<DabSubchannel*> subchannels;
std::vector<std::shared_ptr<AnnouncementCluster> > clusters;
+ std::vector<std::shared_ptr<LinkageSet> > linkagesets;
};
@@ -421,6 +423,57 @@ class DabService : public RemoteControllable
DabService(const DabService& other);
};
+enum class ServiceLinkType {DAB, FM, DRM, AMSS};
+
+/* Represent one link inside a linkage set */
+struct ServiceLink {
+ ServiceLinkType type;
+ uint16_t id;
+ uint8_t ecc;
+};
+
+/* Keep the data out of LinkageSet to make it copyable */
+struct LinkageSetData {
+ std::list<ServiceLink> id_list;
+
+ /* Linkage Set Number is a 12-bit number that identifies the linkage
+ * set in a country (requires coordination between multiplex operators
+ * in a country)
+ */
+ uint16_t lsn;
+
+ bool active; // Remote-controllable
+ bool hard;
+ bool international;
+
+ std::string keyservice; // TODO replace by pointer to service
+
+ /* Return a LinkageSetData with id_list filtered to include
+ * only those links of a given type
+ */
+ LinkageSetData filter_type(ServiceLinkType type);
+};
+
+/* Represents a linkage set linkage sets according to
+ * TS 103 176 Clause 5.2.3 "Linkage sets". This information will
+ * be encoded in FIG 0/6.
+ */
+class LinkageSet : public RemoteControllable {
+ public:
+ LinkageSet(std::string name, uint16_t lsn, bool hard, bool international);
+
+ LinkageSetData data;
+
+ bool is_active(void) const { return data.active; }
+ private:
+ /* Remote control */
+ virtual void set_parameter(const std::string& parameter,
+ const std::string& value);
+
+ /* Getting a parameter always returns a string. */
+ virtual const std::string get_parameter(const std::string& parameter) const;
+};
+
std::vector<DabSubchannel*>::iterator getSubchannel(
std::vector<DabSubchannel*>& subchannels, int id);
diff --git a/src/RemoteControl.h b/src/RemoteControl.h
index 1c830aa..d8b3b6b 100644
--- a/src/RemoteControl.h
+++ b/src/RemoteControl.h
@@ -6,8 +6,9 @@
Copyright (C) 2016
Matthias P. Braendli, matthias.braendli@mpb.li
+ http://www.opendigitalradio.org
+
This module adds remote-control capability to some of the dabmux modules.
- see testremotecontrol/test.cpp for an example of how to use this.
*/
/*
This file is part of ODR-DabMux.
diff --git a/src/fig/FIG0.h b/src/fig/FIG0.h
index 927efc1..446e1ac 100644
--- a/src/fig/FIG0.h
+++ b/src/fig/FIG0.h
@@ -31,6 +31,7 @@
#include "fig/FIG0_2.h"
#include "fig/FIG0_3.h"
#include "fig/FIG0_5.h"
+#include "fig/FIG0_6.h"
#include "fig/FIG0_8.h"
#include "fig/FIG0_9.h"
#include "fig/FIG0_10.h"
diff --git a/src/fig/FIG0_6.cpp b/src/fig/FIG0_6.cpp
new file mode 100644
index 0000000..411d47c
--- /dev/null
+++ b/src/fig/FIG0_6.cpp
@@ -0,0 +1,353 @@
+/*
+ Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications
+ Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "fig/FIG0_6.h"
+#include "utils.h"
+
+namespace FIC {
+
+FIG0_6::FIG0_6(FIGRuntimeInformation *rti) :
+ m_rti(rti),
+ m_initialised(false)
+{
+}
+
+FillStatus FIG0_6::fill(uint8_t *buf, size_t max_size)
+{
+ FillStatus fs;
+ ssize_t remaining = max_size;
+ auto ensemble = m_rti->ensemble;
+
+ if (not m_initialised) {
+ update();
+ m_initialised = true;
+ }
+
+ FIGtype0* fig0 = NULL;
+
+ for (; linkageSetFIG0_6 != linkageSubsets.end();
+ ++linkageSetFIG0_6) {
+
+ const bool PD = false;
+ const bool ILS = linkageSetFIG0_6->international;
+
+ // need to add key service to num_ids
+ const size_t num_ids = 1 + linkageSetFIG0_6->id_list.size();
+
+ const size_t headersize = sizeof(struct FIGtype0_6_header);
+ const int required_size = sizeof(struct FIGtype0_6) + headersize +
+ (num_ids > 0 ?
+ (PD == 0 ? (ILS == 0 ? 2*num_ids : 3*num_ids) : 4*num_ids) :
+ 0);
+
+ if (fig0 == NULL) {
+ if (remaining < 2 + required_size) {
+ break;
+ }
+ fig0 = (FIGtype0*)buf;
+ fig0->FIGtypeNumber = 0;
+ fig0->Length = 1;
+ fig0->CN = 0;
+ fig0->OE = 0;
+ fig0->PD = PD;
+ fig0->Extension = 6;
+
+ buf += 2;
+ remaining -= 2;
+ }
+ else if (remaining < required_size) {
+ break;
+ }
+
+ FIGtype0_6 *fig0_6 = (FIGtype0_6*)buf;
+
+ fig0_6->IdListFlag = (num_ids > 0);
+ fig0_6->LA = linkageSetFIG0_6->active;
+ fig0_6->SH = linkageSetFIG0_6->hard;
+ fig0_6->ILS = ILS;
+ fig0_6->setLSN(linkageSetFIG0_6->lsn);
+
+ fig0->Length += sizeof(struct FIGtype0_6);
+ buf += sizeof(struct FIGtype0_6);
+ remaining -= sizeof(struct FIGtype0_6);
+
+ if (num_ids > 0) {
+ FIGtype0_6_header *header = (FIGtype0_6_header*)buf;
+ header->rfu = 0;
+ if (num_ids > 0x0F) {
+ etiLog.log(error, "Too large number of links for linkage set 0x%04x",
+ linkageSetFIG0_6->lsn);
+ throw MuxInitException();
+ }
+
+ // update() guarantees us that all entries in a linkage set
+ // have the same type
+ for (const auto& l : linkageSetFIG0_6->id_list) {
+ if (l.type != linkageSetFIG0_6->id_list.front().type) {
+ etiLog.log(error, "INTERNAL ERROR: invalid linkage subset 0x%04x",
+ linkageSetFIG0_6->lsn);
+ throw std::runtime_error("INTERNAL ERROR");
+ }
+ }
+
+ switch (linkageSetFIG0_6->id_list.front().type) {
+ case ServiceLinkType::DAB: header->IdLQ = FIG0_6_IDLQ_DAB; break;
+ case ServiceLinkType::FM: header->IdLQ = FIG0_6_IDLQ_RDS; break;
+ case ServiceLinkType::DRM: header->IdLQ = FIG0_6_IDLQ_DRM_AMSS; break;
+ case ServiceLinkType::AMSS: header->IdLQ = FIG0_6_IDLQ_DRM_AMSS; break;
+ }
+ header->rfa = 0;
+ header->num_ids = num_ids;
+
+ fig0->Length += headersize;
+ buf += headersize;
+ remaining -= headersize;
+
+ const std::string keyserviceuid = linkageSetFIG0_6->keyservice;
+ const auto& keyservice = std::find_if(
+ ensemble->services.begin(),
+ ensemble->services.end(),
+ [&](const std::shared_ptr<DabService> srv) {
+ return srv->uid == keyserviceuid;
+ });
+
+ if (keyservice == ensemble->services.end()) {
+ etiLog.log(error, "Invalid key service %s in linkage set 0x%04x",
+ keyserviceuid.c_str(), linkageSetFIG0_6->lsn);
+ throw MuxInitException();
+ }
+
+ if (not PD and not ILS) {
+ buf[0] = (*keyservice)->id >> 8;
+ buf[1] = (*keyservice)->id & 0xFF;
+ fig0->Length += 2;
+ buf += 2;
+ remaining -= 2;
+
+ for (const auto& l : linkageSetFIG0_6->id_list) {
+ buf[0] = l.id >> 8;
+ buf[1] = l.id & 0xFF;
+ fig0->Length += 2;
+ buf += 2;
+ remaining -= 2;
+ }
+ }
+ else if (not PD and ILS) {
+ buf[0] = ensemble->ecc;
+ buf[1] = (*keyservice)->id >> 8;
+ buf[2] = (*keyservice)->id & 0xFF;
+ fig0->Length += 3;
+ buf += 3;
+ remaining -= 3;
+
+ for (const auto& l : linkageSetFIG0_6->id_list) {
+ buf[0] = l.ecc;
+ buf[1] = l.id >> 8;
+ buf[2] = l.id & 0xFF;
+ fig0->Length += 3;
+ buf += 3;
+ remaining -= 3;
+ }
+ }
+ else { // PD == true
+ // TODO if IdLQ is 11, MSB shall be zero
+ buf[0] = (*keyservice)->id >> 24;
+ buf[1] = (*keyservice)->id >> 16;
+ buf[2] = (*keyservice)->id >> 8;
+ buf[3] = (*keyservice)->id & 0xFF;
+ fig0->Length += 4;
+ buf += 4;
+ remaining -= 4;
+
+ for (const auto& l : linkageSetFIG0_6->id_list) {
+ buf[0] = l.id >> 24;
+ buf[1] = l.id >> 16;
+ buf[2] = l.id >> 8;
+ buf[3] = l.id & 0xFF;
+ fig0->Length += 4;
+ buf += 4;
+ remaining -= 4;
+ }
+ }
+ }
+ }
+
+ if (linkageSetFIG0_6 == linkageSubsets.end()) {
+ update();
+ fs.complete_fig_transmitted = true;
+ }
+
+ fs.num_bytes_written = max_size - remaining;
+ return fs;
+}
+
+void FIG0_6::update()
+{
+ linkageSubsets.clear();
+
+ // TODO check if AMSS and DRM have to be put into a single subset
+
+ for (const auto& linkageset : m_rti->ensemble->linkagesets) {
+ const auto lsn = linkageset->data.lsn;
+
+ for (const auto& link : linkageset->data.id_list) {
+ const auto type = link.type;
+
+ const auto subset =
+ std::find_if(linkageSubsets.begin(), linkageSubsets.end(),
+ [&](const LinkageSetData& l) {
+ return not l.id_list.empty() and
+ l.lsn == lsn and l.id_list.front().type == type;
+ });
+
+ if (subset == linkageSubsets.end()) {
+ // A subset with that LSN and type does not exist yet
+ linkageSubsets.push_back( linkageset->data.filter_type(type) );
+ }
+ }
+ }
+
+ linkageSetFIG0_6 = linkageSubsets.begin();
+
+#if 0
+ etiLog.log(info, " Linkage Sets");
+ for (const auto& lsd : linkageSubsets) {
+
+ etiLog.log(info, " LSN 0x%04x", lsd.lsn);
+ etiLog.level(info) << " active " << (lsd.active ? "true" : "false");
+ etiLog.level(info) << " " << (lsd.hard ? "hard" : "soft");
+ etiLog.level(info) << " international " << (lsd.international ? "true" : "false");
+ etiLog.level(info) << " key service " << lsd.keyservice;
+
+ etiLog.level(info) << " ID list";
+ for (const auto& link : lsd.id_list) {
+ switch (link.type) {
+ case ServiceLinkType::DAB:
+ etiLog.level(info) << " type DAB";
+ break;
+ case ServiceLinkType::FM:
+ etiLog.level(info) << " type FM";
+ break;
+ case ServiceLinkType::DRM:
+ etiLog.level(info) << " type DRM";
+ break;
+ case ServiceLinkType::AMSS:
+ etiLog.level(info) << " type AMSS";
+ break;
+ }
+ etiLog.log(info, " id 0x%04x", link.id);
+ if (lsd.international) {
+ etiLog.log(info, " ecc 0x%04x", link.ecc);
+ }
+ }
+ }
+#endif
+}
+
+
+FIG0_6_CEI::FIG0_6_CEI(FIGRuntimeInformation *rti) :
+ m_rti(rti)
+{
+}
+
+FillStatus FIG0_6_CEI::fill(uint8_t *buf, size_t max_size)
+{
+ using namespace std;
+
+ auto ensemble = m_rti->ensemble;
+
+ // We are called every 24ms, and must timeout after 5s
+ const int timeout = 5000/24;
+
+ m_transition.update_state(timeout, ensemble->linkagesets);
+
+ FillStatus fs;
+ ssize_t remaining = max_size;
+
+ FIGtype0* fig0 = NULL;
+
+ // Combine all links into one list
+ set<LinkageSet*> alllinks;
+ for (const auto& l : m_transition.new_entries) {
+ alllinks.insert(l.first.get());
+ }
+ for (const auto& l : m_transition.repeated_entries) {
+ alllinks.insert(l.get());
+ }
+ for (const auto& l : m_transition.disabled_entries) {
+ alllinks.insert(l.first.get());
+ }
+
+ for (auto& link : alllinks) {
+ const bool PD = false;
+ const bool ILS = link->data.international;
+
+ // The CEI does not send list contents
+ const size_t num_ids = 0;
+
+ const size_t headersize = sizeof(struct FIGtype0_6_header);
+ const int required_size = sizeof(struct FIGtype0_6) + headersize +
+ (num_ids > 0 ?
+ (PD == 0 ? (ILS == 0 ? 2*num_ids : 3*num_ids) : 4*num_ids) :
+ 0);
+
+ if (fig0 == NULL) {
+ if (remaining < 2 + required_size) {
+ break;
+ }
+ fig0 = (FIGtype0*)buf;
+ fig0->FIGtypeNumber = 0;
+ fig0->Length = 1;
+ fig0->CN = 1; // This is a CEI
+ fig0->OE = 0;
+ fig0->PD = PD;
+ fig0->Extension = 6;
+
+ buf += 2;
+ remaining -= 2;
+ }
+ else if (remaining < required_size) {
+ break;
+ }
+
+ FIGtype0_6 *fig0_6 = (FIGtype0_6*)buf;
+
+ fig0_6->IdListFlag = (num_ids > 0);
+ fig0_6->LA = link->data.active;
+ fig0_6->SH = link->data.hard;
+ fig0_6->ILS = ILS;
+ fig0_6->setLSN(link->data.lsn);
+
+ fig0->Length += sizeof(struct FIGtype0_6);
+ buf += sizeof(struct FIGtype0_6);
+ remaining -= sizeof(struct FIGtype0_6);
+ }
+
+ fs.complete_fig_transmitted = true;
+ return fs;
+}
+
+}
+
diff --git a/src/fig/FIG0_6.h b/src/fig/FIG0_6.h
new file mode 100644
index 0000000..e970102
--- /dev/null
+++ b/src/fig/FIG0_6.h
@@ -0,0 +1,98 @@
+/*
+ Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications
+ Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+#include <memory>
+
+#include "fig/FIG0structs.h"
+#include "fig/TransitionHandler.h"
+
+namespace FIC {
+
+// FIG type 0/6
+// Service Linking
+//
+// This feature shall use the SIV signalling (see clause 5.2.2.1). The database
+// shall be divided by use of a database key. Changes to the database shall be
+// signalled using the CEI. The first service in the list of services in each
+// part of the database, as divided by the database key, shall be a service
+// carried in the ensemble. This service is called the key service.
+//
+// The database key comprises the OE and P/D flags and the S/H, ILS, and LSN
+// fields.
+class FIG0_6 : public IFIG
+{
+ public:
+ FIG0_6(FIGRuntimeInformation* rti);
+ virtual FillStatus fill(uint8_t *buf, size_t max_size);
+ virtual FIG_rate repetition_rate(void) { return FIG_rate::E; }
+
+ virtual const int figtype(void) const { return 0; }
+ virtual const int figextension(void) const { return 6; }
+
+ private:
+ FIGRuntimeInformation *m_rti;
+ bool m_initialised;
+
+ /* Update the linkageSubsets */
+ void update(void);
+
+ /* A LinkageSet can contain links of different types
+ * (DAB, FM, DRM, AMSS), but the FIG needs to send
+ * different FIGs for each of those, because the IdLQ flag
+ * is common to a list.
+ *
+ * We reorganise all LinkageSets into subsets that have
+ * the same type.
+ */
+ std::vector<LinkageSetData> linkageSubsets;
+ std::vector<LinkageSetData>::iterator linkageSetFIG0_6;
+};
+
+// FIG0/6 needs a change indicator, which is a short-form FIG (i.e. without the list)
+// and with C/N 1. Since this has another rate, it's implemented in another class.
+//
+// This is signalled once per second for a period of five seconds
+// (TS 103 176 5.2.4.3).
+class FIG0_6_CEI : public IFIG
+{
+ public:
+ FIG0_6_CEI(FIGRuntimeInformation* rti);
+ virtual FillStatus fill(uint8_t *buf, size_t max_size);
+ virtual FIG_rate repetition_rate(void) { return FIG_rate::B; }
+
+ virtual const int figtype(void) const { return 0; }
+ virtual const int figextension(void) const { return 6; }
+
+ private:
+ FIGRuntimeInformation *m_rti;
+
+ TransitionHandler<LinkageSet> m_transition;
+};
+
+}
diff --git a/src/fig/FIG0structs.h b/src/fig/FIG0structs.h
index a1e79d3..3b9bfbf 100644
--- a/src/fig/FIG0structs.h
+++ b/src/fig/FIG0structs.h
@@ -152,6 +152,32 @@ struct FIGtype0_5_short {
uint8_t language;
} PACKED;
+struct FIGtype0_6 {
+ uint8_t LSN_high:4; // Linkage Set Number
+ uint8_t ILS:1; // 0=national / 1=international
+ uint8_t SH:1; // 0=Soft link / 1=Hard link
+ uint8_t LA:1; // Linkage actuator
+ uint8_t IdListFlag:1; // Marks the presence of the list
+
+ uint8_t LSN_low; // Linkage Set Number
+
+ void setLSN(uint16_t LSN) {
+ LSN_high = LSN >> 8;
+ LSN_low = LSN & 0xff;
+ }
+} PACKED;
+
+#define FIG0_6_IDLQ_DAB 0x0
+#define FIG0_6_IDLQ_RDS 0x1
+#define FIG0_6_IDLQ_DRM_AMSS 0x3
+
+struct FIGtype0_6_header {
+ uint8_t num_ids:4; // number of Ids to follow in the list
+ uint8_t rfa:1; // must be 0
+ uint8_t IdLQ:2; // Identifier List Qualifier, see above defines
+ uint8_t rfu:1; // must be 0
+} PACKED;
+
struct FIGtype0_8_short {
uint8_t SCIdS:4;
uint8_t rfa_1:3;
diff --git a/src/fig/FIGCarousel.cpp b/src/fig/FIGCarousel.cpp
index 63226e0..2fc6718 100644
--- a/src/fig/FIGCarousel.cpp
+++ b/src/fig/FIGCarousel.cpp
@@ -64,6 +64,8 @@ FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) :
m_fig0_2(&m_rti),
m_fig0_3(&m_rti),
m_fig0_5(&m_rti),
+ m_fig0_6(&m_rti),
+ m_fig0_6_cei(&m_rti),
m_fig0_17(&m_rti),
m_fig0_8(&m_rti),
m_fig1_0(&m_rti),
@@ -94,6 +96,8 @@ FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) :
load_and_allocate(m_fig0_2, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_3, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_5, FIBAllocation::FIB_ANY);
+ load_and_allocate(m_fig0_6, FIBAllocation::FIB_ANY);
+ load_and_allocate(m_fig0_6_cei, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_8, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_13, FIBAllocation::FIB_ANY);
diff --git a/src/fig/FIGCarousel.h b/src/fig/FIGCarousel.h
index b46f4a6..209fe19 100644
--- a/src/fig/FIGCarousel.h
+++ b/src/fig/FIGCarousel.h
@@ -89,6 +89,8 @@ class FIGCarousel {
FIG0_2 m_fig0_2;
FIG0_3 m_fig0_3;
FIG0_5 m_fig0_5;
+ FIG0_6 m_fig0_6;
+ FIG0_6_CEI m_fig0_6_cei;
FIG0_17 m_fig0_17;
FIG0_8 m_fig0_8;
FIG1_0 m_fig1_0;
diff --git a/src/utils.cpp b/src/utils.cpp
index cf57170..4431407 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -366,6 +366,43 @@ void printSubchannels(vector<DabSubchannel*>& subchannels)
}
}
+static void printLinking(const shared_ptr<dabEnsemble> ensemble)
+{
+ etiLog.log(info, " Linkage Sets");
+ for (const auto& linkageset : ensemble->linkagesets) {
+ const auto& lsd = linkageset->data;
+
+ etiLog.level(info) << " set " << linkageset->get_rc_name();
+ etiLog.log(info, " LSN 0x%04x", lsd.lsn);
+ etiLog.level(info) << " active " << (lsd.active ? "true" : "false");
+ etiLog.level(info) << " " << (lsd.hard ? "hard" : "soft");
+ etiLog.level(info) << " international " << (lsd.international ? "true" : "false");
+ etiLog.level(info) << " key service " << lsd.keyservice;
+
+ etiLog.level(info) << " ID list";
+ for (const auto& link : lsd.id_list) {
+ switch (link.type) {
+ case ServiceLinkType::DAB:
+ etiLog.level(info) << " type DAB";
+ break;
+ case ServiceLinkType::FM:
+ etiLog.level(info) << " type FM";
+ break;
+ case ServiceLinkType::DRM:
+ etiLog.level(info) << " type DRM";
+ break;
+ case ServiceLinkType::AMSS:
+ etiLog.level(info) << " type AMSS";
+ break;
+ }
+ etiLog.log(info, " id 0x%04x", link.id);
+ if (lsd.international) {
+ etiLog.log(info, " ecc 0x%04x", link.ecc);
+ }
+ }
+ }
+}
+
void printEnsemble(const shared_ptr<dabEnsemble> ensemble)
{
etiLog.log(info, "Ensemble");
@@ -398,6 +435,8 @@ void printEnsemble(const shared_ptr<dabEnsemble> ensemble)
etiLog.level(info) << cluster->tostring();
}
}
+
+ printLinking(ensemble);
}
long hexparse(const std::string& input)