aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSamuel Hunt <sam@maxxwave.co.uk>2025-12-30 20:39:49 +0000
committerSamuel Hunt <sam@maxxwave.co.uk>2025-12-30 20:39:49 +0000
commitf8b5402727b7e94aecbfb663a601577f97bae5b9 (patch)
tree81afd3bad63d4aef0750ed3fb98bf6555d3e7918 /src
parentcf1ce1f08c0e49a0c6c818135971b201026258c2 (diff)
downloaddabmux-f8b5402727b7e94aecbfb663a601577f97bae5b9.tar.gz
dabmux-f8b5402727b7e94aecbfb663a601577f97bae5b9.tar.bz2
dabmux-f8b5402727b7e94aecbfb663a601577f97bae5b9.zip
Added FIG0/20
Diffstat (limited to 'src')
-rw-r--r--src/ConfigParser.cpp100
-rw-r--r--src/MuxElements.h59
-rw-r--r--src/fig/FIG0.h1
-rw-r--r--src/fig/FIG0_20.cpp221
-rw-r--r--src/fig/FIG0_20.h52
-rw-r--r--src/fig/FIGCarousel.cpp2
-rw-r--r--src/fig/FIGCarousel.h1
7 files changed, 435 insertions, 1 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
index a845bef..d937166 100644
--- a/src/ConfigParser.cpp
+++ b/src/ConfigParser.cpp
@@ -373,6 +373,105 @@ static void parse_other_service_linking(ptree& pt,
} // if other-services present
}
+// Parse the service-component-information section (FIG 0/20)
+static void parse_service_component_information(ptree& pt,
+ std::shared_ptr<dabEnsemble> ensemble)
+{
+ auto pt_sci = pt.get_child_optional("service-component-information");
+ if (pt_sci)
+ {
+ for (const auto& it_sci : *pt_sci) {
+ const string sci_uid = it_sci.first;
+ const ptree pt_entry = it_sci.second;
+
+ auto sci = make_shared<ServiceComponentInformation>();
+
+ try {
+ // Required: Service ID
+ sci->SId = hexparse(pt_entry.get<string>("id"));
+
+ // Required: change type
+ string change_str = pt_entry.get<string>("change");
+ if (change_str == "identity") {
+ sci->change_flags = SCIChangeFlags::IdentityChange;
+ }
+ else if (change_str == "addition") {
+ sci->change_flags = SCIChangeFlags::Addition;
+ }
+ else if (change_str == "local_removal") {
+ sci->change_flags = SCIChangeFlags::LocalRemoval;
+ }
+ else if (change_str == "global_removal") {
+ sci->change_flags = SCIChangeFlags::GlobalRemoval;
+ }
+ else {
+ throw runtime_error("Invalid change type '" + change_str +
+ "' for SCI entry " + sci_uid +
+ " (valid: identity, addition, local_removal, global_removal)");
+ }
+
+ // Optional: SCIdS (default 0 = primary component)
+ sci->SCIdS = pt_entry.get<uint8_t>("scids", 0) & 0x0F;
+
+ // Optional: programme flag (default true for audio services)
+ sci->isProgramme = pt_entry.get<bool>("programme", true);
+
+ // Optional: part-time flag
+ sci->part_time = pt_entry.get<bool>("part_time", false);
+
+ // Optional: SC description
+ auto pt_sc = pt_entry.get_child_optional("sc_description");
+ if (pt_sc) {
+ sci->sc_flag = true;
+ sci->ca_flag = pt_sc->get<bool>("ca", false);
+ sci->ad_flag = pt_sc->get<bool>("data", false); // A/D flag: 0=audio, 1=data
+ sci->SCTy = pt_sc->get<uint8_t>("scty", 0) & 0x3F;
+ }
+
+ // Optional: date-time of change (default: special value = already occurred)
+ auto pt_datetime = pt_entry.get_child_optional("datetime");
+ if (pt_datetime) {
+ sci->date = pt_datetime->get<uint8_t>("date", 0x1F) & 0x1F;
+ sci->hour = pt_datetime->get<uint8_t>("hour", 0x1F) & 0x1F;
+ sci->minute = pt_datetime->get<uint8_t>("minute", 0x3F) & 0x3F;
+ sci->second = pt_datetime->get<uint8_t>("second", 0x3F) & 0x3F;
+ }
+ // else: default values already set to special value (0x1FFFFFF)
+
+ // Optional: transfer SId (for identity changes or service moves)
+ string transfer_sid_str = pt_entry.get<string>("transfer_sid", "");
+ if (not transfer_sid_str.empty()) {
+ sci->sid_flag = true;
+ sci->transfer_sid = hexparse(transfer_sid_str);
+ }
+
+ // Optional: transfer EId (for service moves to another ensemble)
+ string transfer_eid_str = pt_entry.get<string>("transfer_eid", "");
+ if (not transfer_eid_str.empty()) {
+ sci->eid_flag = true;
+ sci->transfer_eid = hexparse(transfer_eid_str);
+ }
+
+ // Optional: active flag (default false - must be enabled via RC or config)
+ sci->set_active(pt_entry.get<bool>("active", false));
+
+ ensemble->sci_entries.push_back(sci);
+
+ etiLog.level(info) << "SCI entry " << sci_uid <<
+ " configured for SId 0x" << hex << sci->SId << dec;
+ }
+ catch (const ptree_error &e) {
+ throw runtime_error("Invalid configuration for SCI entry " +
+ sci_uid + ": " + e.what());
+ }
+ catch (const std::exception &e) {
+ throw runtime_error("Error parsing SCI entry " +
+ sci_uid + ": " + e.what());
+ }
+ } // for over sci entries
+ } // if service-component-information present
+}
+
static void parse_general(ptree& pt,
std::shared_ptr<dabEnsemble> ensemble)
{
@@ -906,6 +1005,7 @@ void parse_ptree(
parse_linkage(pt, ensemble);
parse_freq_info(pt, ensemble);
parse_other_service_linking(pt, ensemble);
+ parse_service_component_information(pt, ensemble);
}
static Inputs::dab_input_zmq_config_t setup_zmq_input(
diff --git a/src/MuxElements.h b/src/MuxElements.h
index 1e9b707..bbd87ec 100644
--- a/src/MuxElements.h
+++ b/src/MuxElements.h
@@ -287,6 +287,63 @@ using vec_sp_service = std::vector<std::shared_ptr<DabService> >;
using vec_sp_subchannel = std::vector<std::shared_ptr<DabSubchannel> >;
+/* FIG 0/20 Service Component Information (SCI)
+ * Change flags values - see ETSI TS 103 176 clause 6
+ */
+enum class SCIChangeFlags : uint8_t {
+ IdentityChange = 0, // 00: Service changing identity or moving ensemble
+ Addition = 1, // 01: Service being added
+ LocalRemoval = 2, // 10: Service removed from this ensemble only
+ GlobalRemoval = 3 // 11: Service removed from all ensembles
+};
+
+/* Service Component Information entry for FIG 0/20
+ * See ETSI TS 103 176 clause 6
+ */
+struct ServiceComponentInformation {
+ uint32_t SId = 0; // Service Identifier (16-bit for audio, 32-bit for data)
+ uint8_t SCIdS = 0; // Service Component Identifier within Service (4-bit)
+ SCIChangeFlags change_flags = SCIChangeFlags::Addition;
+ bool part_time = false; // P-T flag: true if part-time service
+ bool sc_flag = false; // SC flag: true if SC description present
+
+ // SC description (only present if sc_flag is true)
+ bool ca_flag = false; // CA flag: Access control applied
+ bool ad_flag = false; // A/D flag: 0=ASCTy follows, 1=DSCTy follows
+ uint8_t SCTy = 0; // Service Component Type (6-bit)
+
+ // Date-time of change (special value 0x1FFFFFF means "already occurred" or "unknown")
+ uint8_t date = 0x1F; // 5-bit, 0x1F = special value
+ uint8_t hour = 0x1F; // 5-bit, 0x1F = special value
+ uint8_t minute = 0x3F; // 6-bit, 0x3F = special value
+ uint8_t second = 0x3F; // 6-bit, 0x3F = special value
+
+ // Transfer information (for service moves/identity changes)
+ bool sid_flag = false; // SId flag: Transfer SId present
+ bool eid_flag = false; // EId flag: Transfer EId present
+ uint32_t transfer_sid = 0; // Transfer Service Identifier
+ uint16_t transfer_eid = 0; // Transfer Ensemble Identifier
+
+ bool isDateTimeSpecial() const {
+ return (date == 0x1F && hour == 0x1F && minute == 0x3F && second == 0x3F);
+ }
+
+ void setDateTimeSpecial() {
+ date = 0x1F; hour = 0x1F; minute = 0x3F; second = 0x3F;
+ }
+
+ bool isProgramme = true;
+
+ bool is_active() const { return m_active; }
+ void set_active(bool a) { m_active = a; }
+
+private:
+ bool m_active = false;
+};
+
+using vec_sp_sci = std::vector<std::shared_ptr<ServiceComponentInformation> >;
+
+
enum class TransmissionMode_e {
TM_I,
TM_II,
@@ -349,6 +406,7 @@ class dabEnsemble : public RemoteControllable {
std::vector<std::shared_ptr<LinkageSet> > linkagesets;
std::vector<FrequencyInformation> frequency_information;
std::vector<ServiceOtherEnsembleInfo> service_other_ensemble;
+ vec_sp_sci sci_entries;
};
@@ -667,4 +725,3 @@ vec_sp_component::iterator getComponent(
vec_sp_service::iterator getService(
std::shared_ptr<DabComponent> component,
vec_sp_service& services);
-
diff --git a/src/fig/FIG0.h b/src/fig/FIG0.h
index a779fa0..26d55ab 100644
--- a/src/fig/FIG0.h
+++ b/src/fig/FIG0.h
@@ -41,6 +41,7 @@
#include "fig/FIG0_17.h"
#include "fig/FIG0_18.h"
#include "fig/FIG0_19.h"
+#include "fig/FIG0_20.h"
#include "fig/FIG0_21.h"
#include "fig/FIG0_24.h"
diff --git a/src/fig/FIG0_20.cpp b/src/fig/FIG0_20.cpp
new file mode 100644
index 0000000..86ac857
--- /dev/null
+++ b/src/fig/FIG0_20.cpp
@@ -0,0 +1,221 @@
+/*
+ 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) 2024
+ 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/FIG0structs.h"
+#include "fig/FIG0_20.h"
+#include "utils.h"
+
+namespace FIC {
+
+// FIG 0/20 header structure for programme services (16-bit SId)
+struct FIGtype0_20_programme_header {
+ uint8_t SId_high;
+ uint8_t SId_low;
+ uint8_t sc_flag:1;
+ uint8_t pt_flag:1;
+ uint8_t change_flags:2;
+ uint8_t SCIdS:4;
+} PACKED;
+
+// FIG 0/20 header structure for data services (32-bit SId)
+struct FIGtype0_20_data_header {
+ uint8_t SId_byte0;
+ uint8_t SId_byte1;
+ uint8_t SId_byte2;
+ uint8_t SId_byte3;
+ uint8_t sc_flag:1;
+ uint8_t pt_flag:1;
+ uint8_t change_flags:2;
+ uint8_t SCIdS:4;
+} PACKED;
+
+// SC description structure (1 byte, only present if SC flag = 1)
+struct FIGtype0_20_sc_desc {
+ uint8_t SCTy:6;
+ uint8_t ad_flag:1;
+ uint8_t ca_flag:1;
+} PACKED;
+
+FIG0_20::FIG0_20(FIGRuntimeInformation *rti) :
+ m_rti(rti),
+ m_initialised(false)
+{ }
+
+FillStatus FIG0_20::fill(uint8_t *buf, size_t max_size)
+{
+ FillStatus fs;
+ ssize_t remaining = max_size;
+
+ auto ensemble = m_rti->ensemble;
+
+ if (ensemble->sci_entries.empty()) {
+ fs.complete_fig_transmitted = true;
+ fs.num_bytes_written = 0;
+ return fs;
+ }
+
+ if (not m_initialised) {
+ sci_it = ensemble->sci_entries.end();
+ m_initialised = true;
+ }
+
+ FIGtype0* fig0 = NULL;
+
+ for (; sci_it != ensemble->sci_entries.end(); ++sci_it) {
+ auto& sci = *sci_it;
+
+ if (!sci->is_active()) {
+ continue;
+ }
+
+ // Calculate required size for this SCI entry
+ size_t required_size = 0;
+
+ if (sci->isProgramme) {
+ required_size += 3; // 2 bytes SId + 1 byte flags
+ } else {
+ required_size += 5; // 4 bytes SId + 1 byte flags
+ }
+
+ if (sci->sc_flag) {
+ required_size += 1;
+ }
+
+ // Date-time field: 3 bytes
+ required_size += 3;
+
+ // Transfer SId
+ if (sci->sid_flag) {
+ required_size += sci->isProgramme ? 2 : 4;
+ }
+
+ // Transfer EId
+ if (sci->eid_flag) {
+ required_size += 2;
+ }
+
+ if (fig0 == NULL) {
+ if (remaining < 2 + (ssize_t)required_size) {
+ break;
+ }
+
+ fig0 = (FIGtype0*)buf;
+ fig0->FIGtypeNumber = 0;
+ fig0->Length = 1;
+ fig0->CN = 0;
+ fig0->OE = 0;
+ fig0->PD = sci->isProgramme ? 0 : 1;
+ fig0->Extension = 20;
+
+ buf += 2;
+ remaining -= 2;
+ }
+ else if (remaining < (ssize_t)required_size) {
+ break;
+ }
+
+ uint8_t* entry_start = buf;
+
+ if (sci->isProgramme) {
+ auto header = (FIGtype0_20_programme_header*)buf;
+ header->SId_high = (sci->SId >> 8) & 0xFF;
+ header->SId_low = sci->SId & 0xFF;
+ header->SCIdS = sci->SCIdS & 0x0F;
+ header->change_flags = static_cast<uint8_t>(sci->change_flags) & 0x03;
+ header->pt_flag = sci->part_time ? 1 : 0;
+ header->sc_flag = sci->sc_flag ? 1 : 0;
+ buf += 3;
+ }
+ else {
+ auto header = (FIGtype0_20_data_header*)buf;
+ header->SId_byte0 = (sci->SId >> 24) & 0xFF;
+ header->SId_byte1 = (sci->SId >> 16) & 0xFF;
+ header->SId_byte2 = (sci->SId >> 8) & 0xFF;
+ header->SId_byte3 = sci->SId & 0xFF;
+ header->SCIdS = sci->SCIdS & 0x0F;
+ header->change_flags = static_cast<uint8_t>(sci->change_flags) & 0x03;
+ header->pt_flag = sci->part_time ? 1 : 0;
+ header->sc_flag = sci->sc_flag ? 1 : 0;
+ buf += 5;
+ }
+
+ if (sci->sc_flag) {
+ auto sc_desc = (FIGtype0_20_sc_desc*)buf;
+ sc_desc->ca_flag = sci->ca_flag ? 1 : 0;
+ sc_desc->ad_flag = sci->ad_flag ? 1 : 0;
+ sc_desc->SCTy = sci->SCTy & 0x3F;
+ buf += 1;
+ }
+
+ // Date-time field (3 bytes)
+ // Byte 0: date[4:0] + hour[4:2]
+ // Byte 1: hour[1:0] + minute[5:0]
+ // Byte 2: second[5:0] + sid_flag + eid_flag
+ uint8_t date_val = sci->date & 0x1F;
+ uint8_t hour_val = sci->hour & 0x1F;
+ uint8_t minute_val = sci->minute & 0x3F;
+ uint8_t second_val = sci->second & 0x3F;
+
+ buf[0] = (date_val << 3) | (hour_val >> 2);
+ buf[1] = ((hour_val & 0x03) << 6) | minute_val;
+ buf[2] = (second_val << 2) | ((sci->sid_flag ? 1 : 0) << 1) | (sci->eid_flag ? 1 : 0);
+ buf += 3;
+
+ if (sci->sid_flag) {
+ if (sci->isProgramme) {
+ buf[0] = (sci->transfer_sid >> 8) & 0xFF;
+ buf[1] = sci->transfer_sid & 0xFF;
+ buf += 2;
+ }
+ else {
+ buf[0] = (sci->transfer_sid >> 24) & 0xFF;
+ buf[1] = (sci->transfer_sid >> 16) & 0xFF;
+ buf[2] = (sci->transfer_sid >> 8) & 0xFF;
+ buf[3] = sci->transfer_sid & 0xFF;
+ buf += 4;
+ }
+ }
+
+ if (sci->eid_flag) {
+ buf[0] = (sci->transfer_eid >> 8) & 0xFF;
+ buf[1] = sci->transfer_eid & 0xFF;
+ buf += 2;
+ }
+
+ size_t entry_size = buf - entry_start;
+ fig0->Length += entry_size;
+ remaining -= entry_size;
+ }
+
+ if (sci_it == ensemble->sci_entries.end()) {
+ sci_it = ensemble->sci_entries.begin();
+ fs.complete_fig_transmitted = true;
+ }
+
+ fs.num_bytes_written = max_size - remaining;
+ return fs;
+}
+
+}
diff --git a/src/fig/FIG0_20.h b/src/fig/FIG0_20.h
new file mode 100644
index 0000000..2ad6c94
--- /dev/null
+++ b/src/fig/FIG0_20.h
@@ -0,0 +1,52 @@
+/*
+ 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) 2024
+ 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>
+
+namespace FIC {
+
+// FIG type 0/20
+// Service Component Information (SCI)
+// See ETSI TS 103 176 clause 6
+class FIG0_20 : public IFIG
+{
+ public:
+ FIG0_20(FIGRuntimeInformation* rti);
+ virtual FillStatus fill(uint8_t *buf, size_t max_size);
+ virtual FIG_rate repetition_rate() const { return FIG_rate::B; }
+
+ virtual int figtype() const { return 0; }
+ virtual int figextension() const { return 20; }
+
+ private:
+ FIGRuntimeInformation *m_rti;
+ bool m_initialised;
+ vec_sp_sci::iterator sci_it;
+};
+
+}
diff --git a/src/fig/FIGCarousel.cpp b/src/fig/FIGCarousel.cpp
index 4c5c04c..3d627c9 100644
--- a/src/fig/FIGCarousel.cpp
+++ b/src/fig/FIGCarousel.cpp
@@ -89,6 +89,7 @@ FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) :
m_fig1_5(&m_rti),
m_fig0_18(&m_rti),
m_fig0_19(&m_rti),
+ m_fig0_20(&m_rti),
m_fig0_21(&m_rti),
m_fig0_24(&m_rti),
m_fig2_0(&m_rti),
@@ -130,6 +131,7 @@ FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) :
load_and_allocate(m_fig1_5, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_18, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_19, FIBAllocation::FIB_ANY);
+ load_and_allocate(m_fig0_20, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_21, FIBAllocation::FIB_ANY);
load_and_allocate(m_fig0_24, FIBAllocation::FIB_ANY);
diff --git a/src/fig/FIGCarousel.h b/src/fig/FIGCarousel.h
index 00b6d18..b56bec4 100644
--- a/src/fig/FIGCarousel.h
+++ b/src/fig/FIGCarousel.h
@@ -114,6 +114,7 @@ class FIGCarousel {
FIG1_5 m_fig1_5;
FIG0_18 m_fig0_18;
FIG0_19 m_fig0_19;
+ FIG0_20 m_fig0_20;
FIG0_21 m_fig0_21;
FIG0_24 m_fig0_24;
FIG2_0 m_fig2_0;