From f8b5402727b7e94aecbfb663a601577f97bae5b9 Mon Sep 17 00:00:00 2001 From: Samuel Hunt Date: Tue, 30 Dec 2025 20:39:49 +0000 Subject: Added FIG0/20 --- src/ConfigParser.cpp | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) (limited to 'src/ConfigParser.cpp') 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 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(); + + try { + // Required: Service ID + sci->SId = hexparse(pt_entry.get("id")); + + // Required: change type + string change_str = pt_entry.get("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("scids", 0) & 0x0F; + + // Optional: programme flag (default true for audio services) + sci->isProgramme = pt_entry.get("programme", true); + + // Optional: part-time flag + sci->part_time = pt_entry.get("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("ca", false); + sci->ad_flag = pt_sc->get("data", false); // A/D flag: 0=audio, 1=data + sci->SCTy = pt_sc->get("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("date", 0x1F) & 0x1F; + sci->hour = pt_datetime->get("hour", 0x1F) & 0x1F; + sci->minute = pt_datetime->get("minute", 0x3F) & 0x3F; + sci->second = pt_datetime->get("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("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("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("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 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( -- cgit v1.2.3 From b084bd07570cd031cbba4cc0617418883d82a9c7 Mon Sep 17 00:00:00 2001 From: Samuel Hunt Date: Thu, 8 Jan 2026 16:13:19 +0000 Subject: Priority based FIC scheduler capable of >40 services --- Makefile.am | 4 ++++ doc/example.mux | 4 ++++ src/ConfigParser.cpp | 5 +++++ src/DabMultiplexer.cpp | 32 ++++++++++++++++++++++++++++---- src/DabMultiplexer.h | 15 ++++++++++++++- src/MuxElements.h | 2 ++ src/utils.cpp | 3 +++ 7 files changed, 60 insertions(+), 5 deletions(-) (limited to 'src/ConfigParser.cpp') diff --git a/Makefile.am b/Makefile.am index 1cf101a..c1b7731 100644 --- a/Makefile.am +++ b/Makefile.am @@ -138,6 +138,10 @@ odr_dabmux_SOURCES =src/DabMux.cpp \ src/fig/FIG2.h \ src/fig/FIGCarousel.cpp \ src/fig/FIGCarousel.h \ + src/fig/FIGCarouselPriority.cpp \ + src/fig/FIGCarouselPriority.h \ + src/fig/FIGSchedulerType.cpp \ + src/fig/FIGSchedulerType.h \ src/fig/TransitionHandler.h \ src/mpeg.h \ src/mpeg.c \ diff --git a/doc/example.mux b/doc/example.mux index ae12fb2..7a8739b 100644 --- a/doc/example.mux +++ b/doc/example.mux @@ -86,6 +86,10 @@ general { ; system has had time to setup the clock. ;startupcheck "chronyc waitsync 10 0.01" ;startupcheck "ntp-wait -fv" + + ; Enable the Maxxwave priority tree round-robin FIC scheduler that seems to respect repetition rates >40 services + ; FIC-Scheduler default is the default scheduler that seems to fall apart after around 15 services + fic-scheduler priority } remotecontrol { diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 8f4052c..4e20dea 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -55,6 +55,7 @@ #include #include #include +#include "fig/FIGSchedulerType.h" using namespace std; using boost::property_tree::ptree; @@ -532,6 +533,10 @@ static void parse_general(ptree& pt, ensemble->reconfig_counter = pt_ensemble.get("reconfig-counter", ensemble->reconfig_counter); } + // FIG scheduler type selection + std::string fic_scheduler_str = pt_general.get("fic-scheduler", "classic"); + ensemble->fic_scheduler = FIC::parse_scheduler_type(fic_scheduler_str); + string lto_auto = pt_ensemble.get("local-time-offset", ""); if (lto_auto == "auto") { ensemble->lto_auto = true; diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index 7a8ac97..32ed2b3 100644 --- a/src/DabMultiplexer.cpp +++ b/src/DabMultiplexer.cpp @@ -158,8 +158,7 @@ DabMultiplexer::DabMultiplexer(DabMultiplexerConfig& config) : m_config(config), m_time(), ensemble(std::make_shared()), - m_clock_tai(split_pipe_separated_string(m_config.pt.get("general.tai_clock_bulletins", ""))), - fig_carousel(ensemble, [&]() { return m_time.get_milliseconds_seconds(); }) + m_clock_tai(split_pipe_separated_string(m_config.pt.get("general.tai_clock_bulletins", ""))) { RC_ADD_PARAMETER(frames, "Show number of frames generated [read-only]"); RC_ADD_PARAMETER(tist_offset, "Configured tist-offset"); @@ -185,6 +184,20 @@ void DabMultiplexer::prepare(bool require_tai_clock) { parse_ptree(m_config.pt, ensemble); + /* Create the appropriate FIG carousel based on config. + * This must happen after parse_ptree() which sets ensemble->fic_scheduler + */ + m_scheduler_type = ensemble->fic_scheduler; + auto time_func = [&]() { return m_time.get_milliseconds_seconds(); }; + + if (m_scheduler_type == FIC::FIGSchedulerType::Priority) { + etiLog.level(info) << "Using priority-based FIG scheduler"; + m_fig_carousel_priority.reset(new FIC::FIGCarouselPriority(ensemble, time_func)); + } else { + etiLog.level(info) << "Using classic FIG scheduler"; + m_fig_carousel_classic.reset(new FIC::FIGCarousel(ensemble, time_func)); + } + rcs.enrol(this); rcs.enrol(ensemble.get()); @@ -489,6 +502,17 @@ void DabMultiplexer::reload_linkagesets() } } +/* Helper method for FIG carousel write_fibs - abstracts the scheduler type */ +size_t DabMultiplexer::fig_carousel_write_fibs(uint8_t* buf, uint64_t current_frame, bool fib3_present) +{ + if (m_fig_carousel_priority) { + return m_fig_carousel_priority->write_fibs(buf, current_frame, fib3_present); + } else if (m_fig_carousel_classic) { + return m_fig_carousel_classic->write_fibs(buf, current_frame, fib3_present); + } + return 0; +} + /* Each call creates one ETI frame */ void DabMultiplexer::mux_frame(std::vector >& outputs) { @@ -709,9 +733,9 @@ void DabMultiplexer::mux_frame(std::vector >& outputs edi_tagDETI.fic_data = &etiFrame[index]; edi_tagDETI.fic_length = FICL * 4; - // Insert all FIBs + // Insert all FIBs using the selected scheduler const bool fib3_present = (ensemble->transmission_mode == TransmissionMode_e::TM_III); - index += fig_carousel.write_fibs(&etiFrame[index], currentFrame, fib3_present); + index += fig_carousel_write_fibs(&etiFrame[index], currentFrame, fib3_present); /********************************************************************** ****** Input Data Reading ******************************************* diff --git a/src/DabMultiplexer.h b/src/DabMultiplexer.h index 620e65d..afb308f 100644 --- a/src/DabMultiplexer.h +++ b/src/DabMultiplexer.h @@ -32,6 +32,8 @@ #include "dabOutput/dabOutput.h" #include "edioutput/Transport.h" #include "fig/FIGCarousel.h" +#include "fig/FIGCarouselPriority.h" +#include "fig/FIGSchedulerType.h" #include "MuxElements.h" #include "RemoteControl.h" #include "ClockTAI.h" @@ -130,5 +132,16 @@ class DabMultiplexer : public RemoteControllable { bool m_tai_clock_required = false; ClockTAI m_clock_tai; - FIC::FIGCarousel fig_carousel; + /* FIG Carousel - supports classic and priority schedulers + * + * Only one of these will be instantiated based on config. + * The scheduler type is determined by ensemble->fic_scheduler + * which is set during config parsing in prepare(). + */ + FIC::FIGSchedulerType m_scheduler_type = FIC::FIGSchedulerType::Classic; + std::unique_ptr m_fig_carousel_classic; + std::unique_ptr m_fig_carousel_priority; + + /* Helper method for FIG carousel write_fibs */ + size_t fig_carousel_write_fibs(uint8_t* buf, uint64_t current_frame, bool fib3_present); }; diff --git a/src/MuxElements.h b/src/MuxElements.h index 8ba103f..ab05ce3 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -40,6 +40,7 @@ #include "dabOutput/dabOutput.h" #include "input/inputs.h" #include "RemoteControl.h" +#include "fig/FIGSchedulerType.h" // For FIGSchedulerType enum // Protection levels and bitrates for UEP. const unsigned char ProtectionLevelTable[64] = { @@ -410,6 +411,7 @@ class dabEnsemble : public RemoteControllable { std::vector > linkagesets; std::vector frequency_information; std::vector service_other_ensemble; + FIC::FIGSchedulerType fic_scheduler = FIC::FIGSchedulerType::Classic; vec_sp_sci sci_entries; }; diff --git a/src/utils.cpp b/src/utils.cpp index 7ea6293..127ba78 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -31,6 +31,7 @@ #include #include "utils.h" #include "fig/FIG0structs.h" +#include "fig/FIGSchedulerType.h" using namespace std; @@ -496,6 +497,8 @@ void printEnsemble(const shared_ptr& ensemble) break; } + etiLog.level(info) << " FIC sched: " << FIC::scheduler_type_to_string(ensemble->fic_scheduler); + if (ensemble->lto_auto) { time_t now = time(nullptr); struct tm ltime; -- cgit v1.2.3