diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ConfigParser.cpp | 65 | ||||
| -rw-r--r-- | src/ConfigParser.h | 16 | ||||
| -rw-r--r-- | src/DabMultiplexer.cpp | 330 | ||||
| -rw-r--r-- | src/DabMultiplexer.h | 115 | ||||
| -rw-r--r-- | src/DabMux.cpp | 183 | ||||
| -rw-r--r-- | src/DabMux.h | 45 | ||||
| -rw-r--r-- | src/Eti.cpp | 17 | ||||
| -rw-r--r-- | src/Eti.h | 18 | ||||
| -rw-r--r-- | src/Interleaver.cpp | 5 | ||||
| -rw-r--r-- | src/ManagementServer.cpp | 90 | ||||
| -rw-r--r-- | src/ManagementServer.h | 36 | ||||
| -rw-r--r-- | src/MuxElements.cpp | 45 | ||||
| -rw-r--r-- | src/MuxElements.h | 47 | ||||
| -rw-r--r-- | src/PcDebug.h | 62 | ||||
| -rw-r--r-- | src/fig/FIG.h | 10 | ||||
| -rw-r--r-- | src/fig/FIG0_10.cpp | 11 | ||||
| -rw-r--r-- | src/fig/FIG0_21.cpp | 67 | ||||
| -rw-r--r-- | src/fig/FIG0_21.h | 6 | ||||
| -rw-r--r-- | src/fig/FIG0_24.cpp | 31 | ||||
| -rw-r--r-- | src/fig/FIG0_24.h | 3 | ||||
| -rw-r--r-- | src/fig/FIG0_6.cpp | 2 | ||||
| -rw-r--r-- | src/fig/FIG0_6.h | 2 | ||||
| -rw-r--r-- | src/fig/FIG0structs.h | 18 | ||||
| -rw-r--r-- | src/fig/FIG1.h | 13 | ||||
| -rw-r--r-- | src/fig/FIG2.h | 14 | ||||
| -rw-r--r-- | src/fig/FIGCarousel.cpp | 7 | ||||
| -rw-r--r-- | src/fig/FIGCarousel.h | 4 | ||||
| -rw-r--r-- | src/input/Edi.cpp | 7 | ||||
| -rw-r--r-- | src/input/File.cpp | 18 | ||||
| -rw-r--r-- | src/mpeg.h | 15 | ||||
| -rwxr-xr-x | src/test_statsserver.sh | 2 | ||||
| -rw-r--r-- | src/utils.cpp | 47 | ||||
| -rw-r--r-- | src/utils.h | 6 | 
33 files changed, 696 insertions, 661 deletions
| diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 74e627b..785a0da 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2022 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      http://www.opendigitalradio.org @@ -36,16 +36,13 @@  #   include "config.h"  #endif -#include "dabOutput/dabOutput.h"  #include "utils.h" -#include "DabMux.h" -#include "ManagementServer.h"  #include "input/Edi.h"  #include "input/Prbs.h"  #include "input/Zmq.h"  #include "input/File.h"  #include "input/Udp.h" -#include "Eti.h" +#include "fig/FIG0structs.h"  #include <boost/property_tree/ptree.hpp>  #include <boost/algorithm/string/classification.hpp>  #include <boost/algorithm/string/split.hpp> @@ -63,6 +60,12 @@ using namespace std;  using boost::property_tree::ptree;  using boost::property_tree::ptree_error; +constexpr uint16_t DEFAULT_DATA_BITRATE = 384; +constexpr uint16_t DEFAULT_PACKET_BITRATE = 32; + +constexpr uint32_t DEFAULT_SERVICE_ID = 50; + +  static void setup_subchannel_from_ptree(shared_ptr<DabSubchannel>& subchan,          const ptree &pt,          std::shared_ptr<dabEnsemble> ensemble, @@ -107,10 +110,10 @@ static void parse_fig2_label(ptree& pt, DabLabel& label) {  }  // Parse the linkage section -static void parse_linkage(ptree& pt, -        std::shared_ptr<dabEnsemble> ensemble) +void parse_linkage( +        const boost::optional<boost::property_tree::ptree&> pt_linking, +        std::vector<std::shared_ptr<LinkageSet> >& linkage_sets)  { -    auto pt_linking = pt.get_child_optional("linking");      if (pt_linking) {          for (const auto& it : *pt_linking) {              const string setuid = it.first; @@ -129,7 +132,7 @@ static void parse_linkage(ptree& pt,              string service_uid = pt_set.get("keyservice", "");              auto linkageset = make_shared<LinkageSet>(setuid, lsn, active, hard, international); -            linkageset->keyservice = service_uid; // TODO check if it exists +            linkageset->keyservice = service_uid; // existence check is done in validate_linkage_sets()              auto pt_list = pt_set.get_child_optional("list"); @@ -186,17 +189,17 @@ static void parse_linkage(ptree& pt,                      linkageset->id_list.push_back(link);                  }              } -            ensemble->linkagesets.push_back(linkageset); +            linkage_sets.push_back(linkageset);          }      }  }  // Parse the FI section -static void parse_freq_info(ptree& pt, std::shared_ptr<dabEnsemble> ensemble) +void parse_freq_info( +        const boost::optional<boost::property_tree::ptree&> pt_frequency_information, +        std::vector<FrequencyInformation>& frequency_information)  { -    auto pt_frequency_information = pt.get_child_optional("frequency_information"); -    if (pt_frequency_information) -    { +    if (pt_frequency_information) {          for (const auto& it_fi : *pt_frequency_information) {              const string fi_uid = it_fi.first;              const ptree pt_fi = it_fi.second; @@ -309,14 +312,14 @@ static void parse_freq_info(ptree& pt, std::shared_ptr<dabEnsemble> ensemble)                  throw runtime_error("invalid configuration for FI " + fi_uid);              } -            ensemble->frequency_information.emplace_back(move(fi)); +            frequency_information.emplace_back(std::move(fi));          } // for over fi          /* We sort all FI to have the OE=0 first and the OE=1 afterwards, to           * avoid having to send FIG0 headers every time it switches.  */          std::sort( -                ensemble->frequency_information.begin(), -                ensemble->frequency_information.end(), +                frequency_information.begin(), +                frequency_information.end(),                  [](const FrequencyInformation& first,                     const FrequencyInformation& second) {                      const int oe_first = first.other_ensemble ? 1 : 0; @@ -326,12 +329,11 @@ static void parse_freq_info(ptree& pt, std::shared_ptr<dabEnsemble> ensemble)      } // if FI present  } -static void parse_other_service_linking(ptree& pt, -        std::shared_ptr<dabEnsemble> ensemble) +void parse_other_service_linking( +        const boost::optional<boost::property_tree::ptree&> pt_other_services, +        std::vector<ServiceOtherEnsembleInfo>& service_other_ensemble)  { -    auto pt_other_services = pt.get_child_optional("other-services"); -    if (pt_other_services) -    { +    if (pt_other_services) {          for (const auto& it_service : *pt_other_services) {              const string srv_uid = it_service.first;              const ptree pt_srv = it_service.second; @@ -360,7 +362,7 @@ static void parse_other_service_linking(ptree& pt,                          }                      } -                    ensemble->service_other_ensemble.push_back(move(info)); +                    service_other_ensemble.push_back(std::move(info));                  }              }              catch (const std::exception &e) { @@ -907,9 +909,20 @@ void parse_ptree(      } -    parse_linkage(pt, ensemble); -    parse_freq_info(pt, ensemble); -    parse_other_service_linking(pt, ensemble); +    const auto pt_linking = pt.get_child_optional("linking"); +    std::vector<std::shared_ptr<LinkageSet> > linkagesets; +    parse_linkage(pt_linking, linkagesets); + +    const auto pt_frequency_information = pt.get_child_optional("frequency_information"); +    std::vector<FrequencyInformation> frequency_information; +    parse_freq_info(pt_frequency_information, frequency_information); + +    const auto pt_other_services = pt.get_child_optional("other-services"); +    std::vector<ServiceOtherEnsembleInfo> services_other_ensemble; +    parse_other_service_linking(pt_other_services, services_other_ensemble); + +    ensemble->set_linking_config(linkagesets, frequency_information, services_other_ensemble); +  }  static Inputs::dab_input_zmq_config_t setup_zmq_input( diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 9ca6c81..4f4cb9b 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2016 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      The Configuration parser sets up the ensemble according @@ -34,6 +34,18 @@  #include <boost/property_tree/ptree.hpp>  #include <memory> -void parse_ptree(boost::property_tree::ptree& pt, +void parse_ptree( +        boost::property_tree::ptree& pt,          std::shared_ptr<dabEnsemble> ensemble); +void parse_linkage( +        const boost::optional<boost::property_tree::ptree&> pt_linking, +        std::vector<std::shared_ptr<LinkageSet> >& linkage_sets); + +void parse_freq_info( +        const boost::optional<boost::property_tree::ptree&> pt_frequency_information, +        std::vector<FrequencyInformation>& frequency_information); + +void parse_other_service_linking( +        const boost::optional<boost::property_tree::ptree&> pt_other_services, +        std::vector<ServiceOtherEnsembleInfo>& service_other_ensemble); diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index bd1c909..45d9a66 100644 --- a/src/DabMultiplexer.cpp +++ b/src/DabMultiplexer.cpp @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2024 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li     */  /* @@ -23,11 +23,18 @@     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.  */ +#include <cmath>  #include <set>  #include <memory> +#include <boost/property_tree/info_parser.hpp> +#include <boost/property_tree/json_parser.hpp> +  #include "DabMultiplexer.h"  #include "ConfigParser.h" -#include "fig/FIG.h" +#include "ManagementServer.h" +#include "crc.h" +#include "utils.h" +#include "Eti.h"  using namespace std; @@ -44,16 +51,119 @@ static vector<string> split_pipe_separated_string(const std::string& s)      return components;  } -DabMultiplexer::DabMultiplexer( -        boost::property_tree::ptree pt) : +uint64_t MuxTime::init(uint32_t tist_at_fct0_ms, double tist_offset) +{ +    // Things we must guarantee, up to granularity of 24ms: +    // Difference between current time and EDI time = tist_offset +    // TIST of frame 0 = tist_at_fct0_ms +    // In order to achieve the second, we calculate the initial +    // counter value so that FCT0 corresponds to the desired TIST. +    // +    // Changing the tist_offset at runtime will throw off the TIST@FCT0 value +    m_tist_offset_ms = std::lround(tist_offset * 1000); + +    using Sec = chrono::seconds; +    const auto now = chrono::system_clock::now() + +        chrono::milliseconds(std::lround(tist_offset * 1000.0)); + +    const auto offset = now - chrono::time_point_cast<Sec>(now); +    if (offset >= chrono::seconds(1)) { +        throw std::logic_error("Invalid startup offset calculation for TIST! " + +                to_string(chrono::duration_cast<chrono::milliseconds>(offset).count()) + +                " ms"); +    } +    const time_t t_now = chrono::system_clock::to_time_t(chrono::time_point_cast<Sec>(now)); +    const auto offset_ms = chrono::duration_cast<chrono::milliseconds>(offset).count(); + +    m_edi_time = t_now; +    m_pps_offset_ms = std::lround(offset_ms / 24.0) * 24; + +    const auto counter_offset = tist_at_fct0_ms / 24; +    const auto offset_as_count = m_pps_offset_ms / 24; + +    return (250 - counter_offset + offset_as_count) % 250; +} + +constexpr int TIMESTAMP_LEVEL_2_SHIFT = 14; + +void MuxTime::increment_timestamp() +{ +    m_pps_offset_ms += 24; +    if (m_pps_offset_ms >= 1000) { +        m_pps_offset_ms -= 1000; +        m_edi_time += 1; + +        // Also update MNSC time for next time FP==0 +        mnsc_increment_time = true; +    } +} + +void MuxTime::set_tist_offset(double new_tist_offset) +{ +    const int32_t new_tist_offset_ms = std::lround(new_tist_offset * 1000.0); +    int32_t delta = m_tist_offset_ms - new_tist_offset_ms; +    if (delta > 0) { +        while (delta > 0) { +            increment_timestamp(); +            delta -= 24; +        } +    } +    else if (delta < 0) { +        while (delta < 0) { +            m_edi_time -= 1; +            delta += 1000; +        } +        // compensate the we subtracted too much +        while (delta > 0) { +            increment_timestamp(); +            delta -= 24; +        } +    } +    m_tist_offset_ms = new_tist_offset_ms; +} + +std::pair<uint32_t, std::time_t> MuxTime::get_tist_seconds() +{ +    auto timestamp = m_pps_offset_ms * 16384; +    return {timestamp % 0xfa0000, m_edi_time}; +} + +std::pair<uint32_t, std::time_t> MuxTime::get_milliseconds_seconds() +{ +    auto tist_seconds = get_tist_seconds(); +    return {tist_seconds.first >> TIMESTAMP_LEVEL_2_SHIFT, tist_seconds.second}; +} + + +void DabMultiplexerConfig::read(const std::string& filename) +{ +    m_config_file = ""; +    try { +        if (stringEndsWith(filename, ".json")) { +            read_json(filename, pt); +        } +        else { +            read_info(filename, pt); +        } +        m_config_file = filename; +    } +    catch (const boost::property_tree::file_parser_error& e) +    { +        etiLog.level(warn) << "Failed to read " << filename; +    } +} + +DabMultiplexer::DabMultiplexer(DabMultiplexerConfig& config) :      RemoteControllable("mux"), -    m_pt(pt), +    m_config(config), +    m_time(),      ensemble(std::make_shared<dabEnsemble>()), -    m_clock_tai(split_pipe_separated_string(pt.get("general.tai_clock_bulletins", ""))), -    fig_carousel(ensemble) +    m_clock_tai(split_pipe_separated_string(m_config.pt.get("general.tai_clock_bulletins", ""))), +    fig_carousel(ensemble, [&]() { return m_time.get_milliseconds_seconds(); })  {      RC_ADD_PARAMETER(frames, "Show number of frames generated [read-only]"); -    RC_ADD_PARAMETER(tist_offset, "Timestamp offset in integral number of seconds"); +    RC_ADD_PARAMETER(tist_offset, "Configured tist-offset"); +    RC_ADD_PARAMETER(reload_linking, "Write 1 to this parameter to trigger a reload of the linkage sets, frequency info and other-services from the config [write-only]");      rcs.enrol(&m_clock_tai);  } @@ -73,7 +183,7 @@ void DabMultiplexer::set_edi_config(const edi::configuration_t& new_edi_conf)  // Run a set of checks on the configuration  void DabMultiplexer::prepare(bool require_tai_clock)  { -    parse_ptree(m_pt, ensemble); +    parse_ptree(m_config.pt, ensemble);      rcs.enrol(this);      rcs.enrol(ensemble.get()); @@ -99,59 +209,20 @@ void DabMultiplexer::prepare(bool require_tai_clock)          throw MuxInitException();      } -    /* At startup, derive edi_time, TIST and CIF count such that there is -     * a consistency across mux restarts. Ensure edi_time and TIST represent -     * current time. -     * -     * FCT and DLFC are directly derived from m_currentFrame. -     * Every 6s, FCT overflows. DLFC overflows at 5000 every 120s. -     * -     * Keep a granularity of 24ms, which corresponds to the duration of an ETI -     * frame, to get nicer timestamps. -     */ -    using Sec = chrono::seconds; -    const auto now = chrono::system_clock::now(); -    const time_t t_now = chrono::system_clock::to_time_t(chrono::time_point_cast<Sec>(now)); +    const uint32_t tist_at_fct0_ms = m_config.pt.get<double>("general.tist_at_fct0", 0); +    currentFrame = m_time.init(tist_at_fct0_ms, m_config.pt.get<double>("general.tist_offset", 0.0)); +    m_time.mnsc_increment_time = false; -    m_edi_time = t_now - (t_now % 6); -    m_currentFrame = 0; -    time_t edi_time_at_cif0 = t_now - (t_now % 120); -    while (edi_time_at_cif0 < m_edi_time) { -        edi_time_at_cif0 += 6; -        m_currentFrame += 250; -    } +    bool tist_enabled = m_config.pt.get("general.tist", false); -    if (edi_time_at_cif0 != m_edi_time) { -        throw std::logic_error("Invalid startup offset calculation for CIF!"); -    } - -    const auto offset = now - chrono::time_point_cast<Sec>(now); -    if (offset >= chrono::seconds(1)) { -        throw std::logic_error("Invalid startup offset calculation for TIST! " + -                to_string(chrono::duration_cast<chrono::milliseconds>(offset).count()) + -                " ms"); -    } - -    int64_t offset_ms = chrono::duration_cast<chrono::milliseconds>(offset).count(); -    offset_ms += 1000 * (t_now - m_edi_time); - -    m_timestamp = 0; -    while (offset_ms >= 24) { -        increment_timestamp(); -        m_currentFrame++; -        offset_ms -= 24; -    } - -    mnsc_increment_time = false; - -    bool tist_enabled = m_pt.get("general.tist", false); -    m_tist_offset = m_pt.get<int>("general.tist_offset", 0); - -    mnsc_time = m_edi_time + m_tist_offset; +    auto tist_edi_time = m_time.get_tist_seconds(); +    const auto timestamp = tist_edi_time.first; +    const auto edi_time = tist_edi_time.second; +    m_time.mnsc_time = edi_time;      etiLog.log(info, "Startup CIF Count %i with timestamp: %d + %f", -            m_currentFrame, m_edi_time, -            (m_timestamp & 0xFFFFFF) / 16384000.0); +            currentFrame, edi_time, +            (timestamp & 0xFFFFFF) / 16384000.0);      // Try to load offset once @@ -391,15 +462,70 @@ void DabMultiplexer::prepare_data_inputs()      }  } -void DabMultiplexer::increment_timestamp() +void DabMultiplexer::reload_linking()  { -    m_timestamp += 24 << 14; // Shift 24ms by 14 to Timestamp level 2 -    if (m_timestamp > 0xf9FFff) { -        m_timestamp -= 0xfa0000; // Substract 16384000, corresponding to one second -        m_edi_time += 1; +    try { +        DabMultiplexerConfig new_conf; +        new_conf.read(m_config.config_file()); +        if (new_conf.valid()) { -        // Also update MNSC time for next time FP==0 -        mnsc_increment_time = true; +            bool linkage_sets_valid = false; +            std::vector<std::shared_ptr<LinkageSet> > linkagesets; + +            try { +                const auto pt_linking = new_conf.pt.get_child_optional("linking"); +                parse_linkage(pt_linking, linkagesets); + +                etiLog.level(info) << "Validating " << linkagesets.size() << " new linkage sets."; + +                linkage_sets_valid = ensemble->validate_linkage_sets( +                        ensemble->services, linkagesets); +                if (not linkage_sets_valid) { +                    etiLog.level(warn) << "New linkage set validation failed"; +                } +            } +            catch (const std::runtime_error& e) +            { +                etiLog.level(warn) << "Failed to validate new linkage sets: " << e.what(); +            } + + +            bool freq_info_valid = false; +            std::vector<FrequencyInformation> frequency_information; + +            try { +                const auto pt_frequency_information = new_conf.pt.get_child_optional( +                        "frequency_information"); +                parse_freq_info(pt_frequency_information, frequency_information); +                freq_info_valid = true; +            } +            catch (const std::runtime_error& e) +            { +                etiLog.level(warn) << "Failed to validate new frequency info: " << e.what(); +            } + +            bool services_oe_valid = false; +            std::vector<ServiceOtherEnsembleInfo> services_other_ensemble; + +            try { +                const auto pt_other_services = new_conf.pt.get_child_optional("other-services"); +                parse_other_service_linking(pt_other_services, services_other_ensemble); +                services_oe_valid = true; +            } +            catch (const std::runtime_error& e) +            { +                etiLog.level(warn) << "Failed to validate new services in other onsembles: " << e.what(); +            } + +            if (linkage_sets_valid and freq_info_valid and services_oe_valid) { +                ensemble->set_linking_config(linkagesets, frequency_information, services_other_ensemble); +                etiLog.level(info) << "Loaded new linkage sets and frequency info."; +            } +        } +    } +    catch (const std::exception& e) +    { +        etiLog.level(warn) << "Failed to update linkage sets: " << e.what();      }  } @@ -421,7 +547,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      // The above Tag Items will be assembled into a TAG Packet      edi::TagPacket edi_tagpacket(edi_conf.tagpacket_alignment); -    const bool tist_enabled = m_pt.get("general.tist", false); +    const bool tist_enabled = m_config.pt.get("general.tist", false);      int tai_utc_offset = 0;      if (tist_enabled and m_tai_clock_required) { @@ -432,9 +558,14 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs              etiLog.level(error) << "Could not get UTC-TAI offset for EDI timestamp";          }      } -    update_dab_time(); -    const auto edi_time = m_edi_time + m_tist_offset; +    auto tist_edi_time = m_time.get_tist_seconds(); +    const auto timestamp = tist_edi_time.first; +    const auto edi_time = tist_edi_time.second; +    /* +    etiLog.level(debug) << "Frame " << currentFrame << " " << edi_time << +        " + " << (timestamp >> TIMESTAMP_LEVEL_2_SHIFT); +        */      // Initialise the ETI frame      memset(etiFrame, 0, 6144); @@ -450,7 +581,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      //****** Field FSYNC *****//      // See ETS 300 799, 6.2.1.2 -    if ((m_currentFrame & 1) == 0) { +    if ((currentFrame & 1) == 0) {          etiSync->FSYNC = ETI_FSYNC1;      }      else { @@ -468,9 +599,8 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      eti_FC *fc = (eti_FC *) &etiFrame[4];      //****** FCT ******// -    // Incremente for each frame, overflows at 249 -    fc->FCT = m_currentFrame % 250; -    edi_tagDETI.dlfc = m_currentFrame % 5000; +    fc->FCT = currentFrame % 250; +    edi_tagDETI.dlfc = currentFrame % 5000;      //****** FICF ******//      // Fast Information Channel Flag, 1 bit, =1 if FIC present @@ -487,7 +617,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      /* Frame Phase, 3 bit counter, tells the COFDM generator       * when to insert the TII. Is also used by the MNSC.       */ -    fc->FP = edi_tagDETI.fp = m_currentFrame & 0x7; +    fc->FP = edi_tagDETI.fp = currentFrame & 0x7;      //****** MID ******//      //Mode Identity, 2 bits, 01 ModeI, 10 modeII, 11 ModeIII, 00 ModeIV @@ -562,7 +692,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      eoh->MNSC = 0;      struct tm time_tm; -    gmtime_r(&mnsc_time, &time_tm); +    gmtime_r(&m_time.mnsc_time, &time_tm);      switch (fc->FP & 0x3) {          case 0: @@ -574,10 +704,10 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs                  mnsc->rfa = 0;              } -            if (mnsc_increment_time) +            if (m_time.mnsc_increment_time)              { -                mnsc_increment_time = false; -                mnsc_time += 1; +                m_time.mnsc_increment_time = false; +                m_time.mnsc_time += 1;              }              break;          case 1: @@ -621,7 +751,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      // Insert all FIBs      const bool fib3_present = (ensemble->transmission_mode == TransmissionMode_e::TM_III); -    index += fig_carousel.write_fibs(&etiFrame[index], m_currentFrame, fib3_present); +    index += fig_carousel.write_fibs(&etiFrame[index], currentFrame, fib3_present);      /**********************************************************************       ******  Input Data Reading ******************************************* @@ -633,12 +763,13 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs          int sizeSubchannel = subchannel->getSizeByte();          // no need to check enableTist because we always increment the timestamp          int result = subchannel->readFrame(&etiFrame[index], -                        sizeSubchannel, edi_time, tai_utc_offset, m_timestamp); +                        sizeSubchannel, +                        edi_time, tai_utc_offset, timestamp);          if (result < 0) {              etiLog.log(info,                      "Subchannel %d read failed at ETI frame number: %d", -                    subchannel->id, m_currentFrame); +                    subchannel->id, currentFrame);          }          // save pointer to Audio or Data Stream into correct TagESTn for EDI @@ -676,10 +807,10 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      index = (FLtmp + 2 + 1) * 4;      eti_TIST *tist = (eti_TIST *) & etiFrame[index]; -    bool enableTist = m_pt.get("general.tist", false); +    bool enableTist = m_config.pt.get("general.tist", false);      if (enableTist) { -        tist->TIST = htonl(m_timestamp) | 0xff; -        edi_tagDETI.tsta = m_timestamp & 0xffffff; +        tist->TIST = htonl(timestamp) | 0xff; +        edi_tagDETI.tsta = timestamp & 0xffffff;      }      else {          tist->TIST = htonl(0xffffff) | 0xff; @@ -700,7 +831,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs              output->setMetadata(md_edi_time);              shared_ptr<OutputMetadata> md_dlfc = -                make_shared<OutputMetadataDLFC>(m_currentFrame % 5000); +                make_shared<OutputMetadataDLFC>(currentFrame % 5000);              output->setMetadata(md_dlfc);          }      } @@ -716,8 +847,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      Approximate     8 ms            1 ms     3,91 us    488 ns     61 ns      time resolution      */ - -    increment_timestamp(); +    m_time.increment_timestamp();      /**********************************************************************       ***********   Section FRPD   ***************************************** @@ -759,6 +889,12 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs          }          edi_sender->write(edi_tagpacket); + +        for (const auto& stat : edi_sender->get_tcp_server_stats()) { +            get_mgmt_server().update_edi_tcp_output_stat( +                    stat.listen_port, +                    stat.stats.size()); +        }      }  #if _DEBUG @@ -769,7 +905,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs          if (enableTist) {              etiLog.log(info, "ETI frame number %i Timestamp: %d + %f",                      m_currentFrame, edi_time, -                    (m_timestamp & 0xFFFFFF) / 16384000.0); +                    (timestamp & 0xFFFFFF) / 16384000.0);          }          else {              etiLog.log(info, "ETI frame number %i Time: %d, no TIST", @@ -778,7 +914,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      }  #endif -    m_currentFrame++; +    currentFrame++;  }  void DabMultiplexer::print_info() @@ -799,7 +935,10 @@ void DabMultiplexer::set_parameter(const std::string& parameter,          throw ParameterError(ss.str());      }      else if (parameter == "tist_offset") { -        m_tist_offset = std::stoi(value); +        m_time.set_tist_offset(std::stod(value)); +    } +    else if (parameter == "reload_linking") { +        reload_linking();      }      else {          stringstream ss; @@ -814,10 +953,15 @@ const std::string DabMultiplexer::get_parameter(const std::string& parameter) co  {      stringstream ss;      if (parameter == "frames") { -        ss << m_currentFrame; +        ss << currentFrame;      }      else if (parameter == "tist_offset") { -        ss << m_tist_offset; +        ss << m_time.tist_offset(); +    } +    else if (parameter == "reload_linking") { +        ss << "Parameter '" << parameter << +            "' is not write-only in controllable " << get_rc_name(); +        throw ParameterError(ss.str());      }      else {          ss << "Parameter '" << parameter << @@ -831,8 +975,8 @@ const std::string DabMultiplexer::get_parameter(const std::string& parameter) co  const json::map_t DabMultiplexer::get_all_values() const  {      json::map_t map; -    map["frames"].v = m_currentFrame; -    map["tist_offset"].v = m_tist_offset; +    map["frames"].v = currentFrame; +    map["tist_offset"].v = m_time.tist_offset();      return map;  } diff --git a/src/DabMultiplexer.h b/src/DabMultiplexer.h index 89d547e..0d95c11 100644 --- a/src/DabMultiplexer.h +++ b/src/DabMultiplexer.h @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2024 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li     */  /* @@ -30,21 +30,12 @@  #endif  #include "dabOutput/dabOutput.h" -#include "edioutput/TagItems.h" -#include "edioutput/TagPacket.h" -#include "edioutput/AFPacket.h"  #include "edioutput/Transport.h"  #include "fig/FIGCarousel.h" -#include "crc.h" -#include "utils.h" -#include "Socket.h" -#include "PcDebug.h"  #include "MuxElements.h"  #include "RemoteControl.h" -#include "Eti.h"  #include "ClockTAI.h"  #include <vector> -#include <chrono>  #include <memory>  #include <string>  #include <memory> @@ -52,20 +43,61 @@  constexpr uint32_t ETI_FSYNC1 = 0x49C5F8; +class MuxTime { +    private: +        std::time_t m_edi_time = 0; +        uint32_t m_pps_offset_ms = 0; +        int64_t m_tist_offset_ms = 0; + +    public: +        std::pair<uint32_t, std::time_t> get_tist_seconds(); +        std::pair<uint32_t, std::time_t> get_milliseconds_seconds(); + + +        /* Pre v3 odr-dabmux did the MNSC calculation differently, +         * which works with the easydabv2. The rework in odr-dabmux, +         * deriving MNSC time from EDI time broke this. +         * +         * That's why we're now tracking MNSC time in separate variables, +         * to get the same behaviour back. +         * +         * I'm not aware of any devices using MNSC time besides the +         * easydab. ODR-DabMod now considers EDI seconds or ZMQ metadata. +         */ +        bool mnsc_increment_time = false; +        std::time_t mnsc_time = 0; + +        /* Setup the time and return the initial currentFrame counter value */ +        uint64_t init(uint32_t tist_at_fct0_ms, double tist_offset); +        void increment_timestamp(); +        double tist_offset() const { return m_tist_offset_ms / 1000.0; } +        void set_tist_offset(double new_tist_offset); +}; + +class DabMultiplexerConfig { +    public: +        boost::property_tree::ptree pt; + +        void read(const std::string& filename); +        bool valid() const { return m_config_file != ""; } +        std::string config_file() const { return m_config_file; } + +    private: +        std::string m_config_file; +}; +  class DabMultiplexer : public RemoteControllable {      public: -        DabMultiplexer(boost::property_tree::ptree pt); +        DabMultiplexer(DabMultiplexerConfig& config);          DabMultiplexer(const DabMultiplexer& other) = delete;          DabMultiplexer& operator=(const DabMultiplexer& other) = delete; -        ~DabMultiplexer(); +        virtual ~DabMultiplexer();          void prepare(bool require_tai_clock); -        uint64_t getCurrentFrame() const { return m_currentFrame; } -          void mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs); -        void print_info(void); +        void print_info();          void set_edi_config(const edi::configuration_t& new_edi_conf); @@ -79,63 +111,24 @@ class DabMultiplexer : public RemoteControllable {          virtual const json::map_t get_all_values() const;      private: -        void prepare_subchannels(void); -        void prepare_services_components(void); -        void prepare_data_inputs(void); -        void increment_timestamp(void); +        void prepare_subchannels(); +        void prepare_services_components(); +        void prepare_data_inputs(); -        boost::property_tree::ptree m_pt; +        void reload_linking(); -        uint32_t m_timestamp = 0; -        std::time_t m_edi_time = 0; +        DabMultiplexerConfig& m_config; -        /* Pre v3 odr-dabmux did the MNSC calculation differently, -         * which works with the easydabv2. The rework in odr-dabmux, -         * deriving MNSC time from EDI time broke this. -         * -         * That's why we're now tracking MNSC time in separate variables, -         * to get the same behaviour back. -         * -         * I'm not aware of any devices using MNSC time besides the -         * easydab. ODR-DabMod now considers EDI seconds or ZMQ metadata. -         */ -        bool mnsc_increment_time = false; -        std::time_t mnsc_time = 0; +        MuxTime m_time; +        uint64_t currentFrame = 0;          edi::configuration_t edi_conf;          std::shared_ptr<edi::Sender> edi_sender; -        uint64_t m_currentFrame = 0; -          std::shared_ptr<dabEnsemble> ensemble; -        int m_tist_offset = 0;          bool m_tai_clock_required = false;          ClockTAI m_clock_tai; -        /* New FIG Carousel */          FIC::FIGCarousel fig_carousel;  }; - -// DAB Mode -#define DEFAULT_DAB_MODE    1 - -// Taille de la trame de donnee, sous-canal 3, nb de paquets de 64bits, -// STL3 * 8 = x kbytes par trame ETI - -// Data bitrate in kbits/s. Must be 64 kb/s multiple. -#define DEFAULT_DATA_BITRATE    384 -#define DEFAULT_PACKET_BITRATE  32 - -/* default ensemble parameters. Label must be max 16 chars, short label - * a subset of the label, max 8 chars - */ -#define DEFAULT_ENSEMBLE_LABEL          "ODR Dab Mux" -#define DEFAULT_ENSEMBLE_SHORT_LABEL    "ODRMux" -#define DEFAULT_ENSEMBLE_ID             0xc000 -#define DEFAULT_ENSEMBLE_ECC            0xa1 - -// start value for default service IDs (if not overridden by configuration) -#define DEFAULT_SERVICE_ID      50 -#define DEFAULT_PACKET_ADDRESS  0 - diff --git a/src/DabMux.cpp b/src/DabMux.cpp index 1a367da..7b5f5d6 100644 --- a/src/DabMux.cpp +++ b/src/DabMux.cpp @@ -29,83 +29,25 @@  #   include "config.h"  #endif -#include <stdlib.h>  #include <memory>  #include <boost/property_tree/ptree.hpp> -#include <boost/property_tree/info_parser.hpp> -#include <boost/property_tree/json_parser.hpp> +#include <ctime> +#include <cstdlib>  #include <cstdio> -#include <iostream> -#include <fstream> -#include <iomanip>  #include <cstring> +#include <cmath>  #include <string> -#include <errno.h> -#include <sys/types.h> -#include <sys/stat.h>  #include <fcntl.h>  #include <signal.h> -// for basename -#include <libgen.h> - -#include <iterator>  #include <vector> -#include <list>  #include <set> -#include <map> -#include <functional> -#include <algorithm> - -#ifdef _WIN32 -#   include <time.h> -#   include <process.h> -#   include <io.h> -#   include <conio.h> -#   include <winsock2.h> // For types... -typedef u_char uint8_t; -typedef WORD uint16_t; -typedef DWORD32 uint32_t; - -#   ifndef __MINGW32__ -#       include "xgetopt.h" -#   endif -#   define read _read -#   define snprintf _snprintf  -#   define sleep(a) Sleep((a) * 1000) -#else -#   include <netinet/in.h> -#   include <unistd.h> -#   include <sys/time.h> -#   include <sys/wait.h> -#   include <sys/socket.h> -#   include <sys/ioctl.h> -#   include <sys/times.h> -#   include <sys/resource.h> - -#endif -#include <time.h> - -#ifdef _WIN32 -#   pragma warning ( disable : 4103 ) -#   include "Eti.h" -#   pragma warning ( default : 4103 ) -#else -#   include "Eti.h" -#endif - -#include "input/Prbs.h" -#include "input/Zmq.h" +#include "DabMultiplexer.h"  #include "dabOutput/dabOutput.h" -#include "crc.h" -#include "Socket.h" -#include "PcDebug.h" -#include "DabMux.h"  #include "MuxElements.h"  #include "utils.h" -#include "ConfigParser.h"  #include "ManagementServer.h"  #include "Log.h"  #include "RemoteControl.h" @@ -120,14 +62,10 @@ volatile sig_atomic_t running = 1;   */  void signalHandler(int signum)  { -#ifdef _WIN32 -    fprintf(stderr, "\npid: %i\n", _getpid()); -#else      fprintf(stderr, "\npid: %i, ppid: %i\n", getpid(), getppid()); -#endif +  #define SIG_MSG "Signal received: "      switch (signum) { -#ifndef _WIN32      case SIGHUP:          fprintf(stderr, SIG_MSG "SIGHUP\n");          break; @@ -138,7 +76,6 @@ void signalHandler(int signum)          fprintf(stderr, SIG_MSG "SIGPIPE\n");          return;          break; -#endif      case SIGINT:          fprintf(stderr, SIG_MSG "SIGINT\n");          break; @@ -150,9 +87,7 @@ void signalHandler(int signum)      default:          fprintf(stderr, SIG_MSG "number %i\n", signum);      } -#ifndef _WIN32      killpg(0, SIGPIPE); -#endif      running = 0;  } @@ -185,12 +120,6 @@ int main(int argc, char *argv[])          }      } -#ifdef _WIN32 -    if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0) { -        etiLog.log(warn, "Can't increase priority: %s\n", -                strerror(errno)); -    } -#else      // Use the lowest real-time priority for this thread, and switch to real-time scheduling      const int policy = SCHED_RR;      sched_param sp; @@ -199,15 +128,15 @@ int main(int argc, char *argv[])      if (thread_prio_ret != 0) {          etiLog.level(error) << "Could not set real-time priority for thread:" << thread_prio_ret;      } -#endif      int returnCode = 0; -    ptree pt;      std::vector<std::shared_ptr<DabOutput> > outputs;      try {          string conf_file = ""; +        DabMultiplexerConfig mux_conf; +          if (argc == 2) { // Assume the only argument is a config file              conf_file = argv[1]; @@ -224,8 +153,7 @@ int main(int argc, char *argv[])                  }                  conf_file = argv[2]; - -                read_info(conf_file, pt); +                mux_conf.read(conf_file);              }              catch (runtime_error &e) {                  throw MuxInitException(e.what()); @@ -238,23 +166,18 @@ int main(int argc, char *argv[])          }          try { -            if (stringEndsWith(conf_file, ".json")) { -                read_json(conf_file, pt); -            } -            else { -                read_info(conf_file, pt); -            } +            mux_conf.read(conf_file);          }          catch (runtime_error &e) {              throw MuxInitException(e.what());          }          /* Enable Logging to syslog conditionally */ -        if (pt.get<bool>("general.syslog", false)) { +        if (mux_conf.pt.get<bool>("general.syslog", false)) {              etiLog.register_backend(std::make_shared<LogToSyslog>());          } -        const auto startupcheck = pt.get<string>("general.startupcheck", ""); +        const auto startupcheck = mux_conf.pt.get<string>("general.startupcheck", "");          if (not startupcheck.empty()) {              etiLog.level(info) << "Running startup check '" << startupcheck << "'";              int wstatus = system(startupcheck.c_str()); @@ -274,26 +197,26 @@ int main(int argc, char *argv[])              }          } -        int mgmtserverport = pt.get<int>("general.managementport", -                             pt.get<int>("general.statsserverport", 0) ); +        int mgmtserverport = mux_conf.pt.get<int>("general.managementport", +                             mux_conf.pt.get<int>("general.statsserverport", 0) );          /* Management: stats and config server */          get_mgmt_server().open(mgmtserverport);          /************** READ REMOTE CONTROL PARAMETERS *************/ -        int telnetport = pt.get<int>("remotecontrol.telnetport", 0); +        int telnetport = mux_conf.pt.get<int>("remotecontrol.telnetport", 0);          if (telnetport != 0) {              auto rc = std::make_shared<RemoteControllerTelnet>(telnetport);              rcs.add_controller(rc);          } -        auto zmqendpoint = pt.get<string>("remotecontrol.zmqendpoint", ""); +        auto zmqendpoint = mux_conf.pt.get<string>("remotecontrol.zmqendpoint", "");          if (not zmqendpoint.empty()) {              auto rc = std::make_shared<RemoteControllerZmq>(zmqendpoint);              rcs.add_controller(rc);          } -        DabMultiplexer mux(pt); +        DabMultiplexer mux(mux_conf);          etiLog.level(info) <<                  PACKAGE_NAME << " " << @@ -310,7 +233,7 @@ int main(int argc, char *argv[])          /******************** READ OUTPUT PARAMETERS ***************/          set<string> all_output_names;          bool output_require_tai_clock = false; -        ptree pt_outputs = pt.get_child("outputs"); +        ptree pt_outputs = mux_conf.pt.get_child("outputs");          for (auto ptree_pair : pt_outputs) {              string outputuid = ptree_pair.first; @@ -327,17 +250,48 @@ int main(int argc, char *argv[])              if (outputuid == "edi") {                  ptree pt_edi = pt_outputs.get_child("edi"); +                bool default_enable_pft = pt_edi.get<bool>("enable_pft", false); +                edi_conf.verbose = pt_edi.get<bool>("verbose", false); + +                unsigned int default_fec = pt_edi.get<unsigned int>("fec", 3); +                unsigned int default_chunk_len = pt_edi.get<unsigned int>("chunk_len", 207); + +                auto check_spreading_factor = [](int percent) { +                    if (percent < 0) { +                        throw std::runtime_error("EDI output: negative packet_spread value is invalid."); +                    } +                    double factor = (double)percent / 100.0; +                    if (factor > 30000) { +                        throw std::runtime_error("EDI output: interleaving set for more than 30 seconds!"); +                    } +                    return factor; +                }; + +                double default_spreading_factor = check_spreading_factor(pt_edi.get<int>("packet_spread", 95)); + +                using pt_t = boost::property_tree::basic_ptree<std::basic_string<char>, std::basic_string<char>>; +                auto handle_overrides = [&](edi::pft_settings_t& pft_settings, pt_t pt) { +                    pft_settings.chunk_len = pt.get<unsigned int>("chunk_len", default_chunk_len); +                    pft_settings.enable_pft = pt.get<bool>("enable_pft", default_enable_pft); +                    pft_settings.fec = pt.get<unsigned int>("fec", default_fec); +                    pft_settings.fragment_spreading_factor = default_spreading_factor; +                    if (auto override_spread_percent = pt.get_optional<int>("packet_spread")) +                        pft_settings.fragment_spreading_factor = check_spreading_factor(*override_spread_percent); + +                    pft_settings.verbose = pt.get<bool>("verbose", edi_conf.verbose); +                }; +                  for (auto pt_edi_dest : pt_edi.get_child("destinations")) {                      const auto proto = pt_edi_dest.second.get<string>("protocol", "udp");                      if (proto == "udp") {                          auto dest = make_shared<edi::udp_destination_t>();                          dest->dest_addr   = pt_edi_dest.second.get<string>("destination"); -                        dest->ttl         = pt_edi_dest.second.get<unsigned int>("ttl", 1); +                        if (auto ttl = pt_edi_dest.second.get_optional<unsigned int>("ttl")) +                            dest->ttl = *ttl;                          dest->source_addr = pt_edi_dest.second.get<string>("source", ""); -                        dest->source_port = pt_edi_dest.second.get<unsigned int>("sourceport"); - -                        dest->dest_port       = pt_edi_dest.second.get<unsigned int>("port", 0); +                        dest->source_port = pt_edi_dest.second.get<unsigned int>("sourceport", 0); +                        dest->dest_port   = pt_edi_dest.second.get<unsigned int>("port", 0);                          if (dest->dest_port == 0) {                              // Compatiblity: we have removed the transport and addressing in the                              // PFT layer, which removed the requirement that all outputs must share @@ -346,6 +300,8 @@ int main(int argc, char *argv[])                              dest->dest_port       = pt_edi.get<unsigned int>("port");                          } +                        handle_overrides(dest->pft_settings, pt_edi_dest.second); +                          edi_conf.destinations.push_back(dest);                      }                      else if (proto == "tcp") { @@ -355,6 +311,8 @@ int main(int argc, char *argv[])                          double preroll = pt_edi_dest.second.get<double>("preroll-burst", 0.0);                          dest->tcp_server_preroll_buffers = ceil(preroll / 24e-3); +                        handle_overrides(dest->pft_settings, pt_edi_dest.second); +                          edi_conf.destinations.push_back(dest);                      }                      else { @@ -362,22 +320,6 @@ int main(int argc, char *argv[])                      }                  } -                edi_conf.dump = pt_edi.get<bool>("dump", false); -                edi_conf.enable_pft = pt_edi.get<bool>("enable_pft", false); -                edi_conf.verbose = pt_edi.get<bool>("verbose", false); - -                edi_conf.fec = pt_edi.get<unsigned int>("fec", 3); -                edi_conf.chunk_len = pt_edi.get<unsigned int>("chunk_len", 207); - -                int spread_percent = pt_edi.get<int>("packet_spread", 95); -                if (spread_percent < 0) { -                    throw std::runtime_error("EDI output: negative packet_spread value is invalid."); -                } -                edi_conf.fragment_spreading_factor = (double)spread_percent / 100.0; -                if (edi_conf.fragment_spreading_factor > 30000) { -                    throw std::runtime_error("EDI output: interleaving set for more than 30 seconds!"); -                } -                  edi_conf.tagpacket_alignment = pt_edi.get<unsigned int>("tagpacket_alignment", 8);                  mux.set_edi_config(edi_conf); @@ -495,7 +437,6 @@ int main(int argc, char *argv[])                  }                  outputs.push_back(output); -              }          } @@ -515,7 +456,7 @@ int main(int argc, char *argv[])              edi_conf.print();          } -        size_t limit = pt.get("general.nbframes", 0); +        const size_t limit = mux_conf.pt.get("general.nbframes", 0);          etiLog.level(info) << "Start loop";          /*   Each iteration of the main loop creates one ETI frame */ @@ -524,6 +465,7 @@ int main(int argc, char *argv[])              mux.mux_frame(outputs);              if (limit && currentFrame >= limit) { +                etiLog.level(info) << "Max number of ETI frames reached: " << currentFrame;                  break;              } @@ -542,17 +484,12 @@ int main(int argc, char *argv[])                      mgmt_server.restart();                  } -                mgmt_server.update_ptree(pt); +                mgmt_server.update_ptree(mux_conf.pt);              }          } - -        if (limit) { -            etiLog.level(info) << "Max number of ETI frames reached: " << currentFrame; -        }      }      catch (const MuxInitException& except) { -        etiLog.level(error) << "Multiplex initialisation aborted: " << -            except.what(); +        etiLog.level(error) << "Multiplex initialisation aborted: " << except.what();          returnCode = 1;      }      catch (const std::invalid_argument& except) { diff --git a/src/DabMux.h b/src/DabMux.h deleted file mode 100644 index 80b4881..0000000 --- a/src/DabMux.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -   Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -   2011 Her Majesty the Queen in Right of Canada (Communications -   Research Center Canada) - -   Copyright (C) 2014 -   Matthias P. Braendli, matthias.braendli@mpb.li - -   This file declares several structures used in the multiplexer, -   and defines default values for some parameters. -   */ -/* -   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 <stdint.h> -#include <string> -#include <vector> -#include "DabMultiplexer.h" -#include "RemoteControl.h" -#include "dabOutput/dabOutput.h" -#include "input/inputs.h" -#include "Eti.h" -#include "MuxElements.h" - -#ifdef _WIN32 -#   include <time.h> -#else -#   include <sys/time.h> -#endif - diff --git a/src/Eti.cpp b/src/Eti.cpp index e1b51fb..2f26f2d 100644 --- a/src/Eti.cpp +++ b/src/Eti.cpp @@ -22,19 +22,10 @@     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.     */ -#ifdef _WIN32 -#   pragma warning ( disable : 4103 ) -#   include "Eti.h" -#   pragma warning ( default : 4103 ) -#else -#   include "Eti.h" -#   include <time.h> -#endif - +#include "Eti.h"  //definitions des structures des champs du ETI(NI, G703) -  unsigned short eti_FC::getFrameLength()  {      return (unsigned short)((FL_high << 8) | FL_low); @@ -80,7 +71,7 @@ void eti_MNSC_TIME_1::setFromTime(struct tm *time_tm)  {      second_unit = time_tm->tm_sec % 10;      second_tens = time_tm->tm_sec / 10; -     +      minute_unit = time_tm->tm_min % 10;      minute_tens = time_tm->tm_min / 10;  } @@ -89,7 +80,7 @@ void eti_MNSC_TIME_2::setFromTime(struct tm *time_tm)  {      hour_unit = time_tm->tm_hour % 10;      hour_tens = time_tm->tm_hour / 10; -     +      day_unit = time_tm->tm_mday % 10;      day_tens = time_tm->tm_mday / 10;  } @@ -98,7 +89,7 @@ void eti_MNSC_TIME_3::setFromTime(struct tm *time_tm)  {      month_unit = (time_tm->tm_mon + 1) % 10;      month_tens = (time_tm->tm_mon + 1) / 10; -     +      // They didn't see the y2k bug coming, did they ?      year_unit = (time_tm->tm_year - 100) % 10;      year_tens = (time_tm->tm_year - 100) / 10; @@ -29,24 +29,12 @@  # include <config.h>  #endif -#ifdef _WIN32 -#	include <winsock2.h>	// For types... -typedef WORD uint16_t; -typedef DWORD32 uint32_t; - -#   define PACKED -#   pragma pack(push, 1) -#else -#   include <stdint.h> -#   include <time.h> - -#   define PACKED __attribute__ ((packed)) -#endif - +#include <cstdint> +#include <ctime> +#define PACKED __attribute__ ((packed))  //definitions des structures des champs du ETI(NI, G703) -  struct eti_SYNC {      uint32_t ERR:8;      uint32_t FSYNC:24; diff --git a/src/Interleaver.cpp b/src/Interleaver.cpp index cf0d235..1786d08 100644 --- a/src/Interleaver.cpp +++ b/src/Interleaver.cpp @@ -23,11 +23,6 @@  #include <string.h> -#ifdef _WIN32 -#   define bzero(a, b) memset((a), 0, (b)) -#endif // _WIN32 - -  Interleaver::Interleaver(unsigned short I, unsigned short M, bool reverse) :      I(I),      M(M), diff --git a/src/ManagementServer.cpp b/src/ManagementServer.cpp index 568e80e..7344b8b 100644 --- a/src/ManagementServer.cpp +++ b/src/ManagementServer.cpp @@ -2,7 +2,7 @@     Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2018 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      http://www.opendigitalradio.org @@ -28,13 +28,12 @@     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.     */ -#include <errno.h> -#include <string.h> -#include <math.h> -#include <stdint.h> -#include <limits>  #include <sstream>  #include <algorithm> +#include <cstring> +#include <cmath> +#include <cstdint> +#include <limits>  #include <boost/version.hpp>  #include "ManagementServer.h"  #include "Log.h" @@ -96,11 +95,6 @@ INPUT_COUNTER_RESET_TIME = std::chrono::minutes(30);  static constexpr int  INPUT_UNSTABLE_THRESHOLD = 3; -/* For how long the input buffers must be empty before we move an input to the - * NoData state.  */ -static constexpr auto -INPUT_NODATA_TIMEOUT  = std::chrono::seconds(30); -  /* Keep 30s of min/max buffer fill information so that we can catch meaningful   * values even if we have a slow poller */  static constexpr auto @@ -127,37 +121,42 @@ ManagementServer& get_mgmt_server()       */  } -void ManagementServer::registerInput(InputStat* is) +void ManagementServer::register_input(InputStat* is)  {      unique_lock<mutex> lock(m_statsmutex);      std::string id(is->get_name()); -    if (m_inputStats.count(id) == 1) { +    if (m_input_stats.count(id) == 1) {          etiLog.level(error) <<              "Double registration in MGMT Server with id '" <<              id << "'";          return;      } -    m_inputStats[id] = is; +    m_input_stats[id] = is;  } -void ManagementServer::unregisterInput(std::string id) +void ManagementServer::unregister_input(std::string id)  {      unique_lock<mutex> lock(m_statsmutex); -    if (m_inputStats.count(id) == 1) { -        m_inputStats.erase(id); +    if (m_input_stats.count(id) == 1) { +        m_input_stats.erase(id);      }  } +// outputs will never disappear, no need to have a "remove" logic +void ManagementServer::update_edi_tcp_output_stat(uint16_t listen_port, size_t num_connections) +{ +    m_output_stats[listen_port] = num_connections; +}  bool ManagementServer::isInputRegistered(std::string& id)  {      unique_lock<mutex> lock(m_statsmutex); -    if (m_inputStats.count(id) == 0) { +    if (m_input_stats.count(id) == 0) {          etiLog.level(error) <<              "Management Server: id '" <<              id << "' does was not registered"; @@ -166,7 +165,7 @@ bool ManagementServer::isInputRegistered(std::string& id)      return true;  } -std::string ManagementServer::getStatConfigJSON() +std::string ManagementServer::get_input_config_json()  {      unique_lock<mutex> lock(m_statsmutex); @@ -175,7 +174,7 @@ std::string ManagementServer::getStatConfigJSON()      std::map<std::string,InputStat*>::iterator iter;      int i = 0; -    for(iter = m_inputStats.begin(); iter != m_inputStats.end(); +    for (iter = m_input_stats.begin(); iter != m_input_stats.end();              ++iter, i++)      {          std::string id = iter->first; @@ -192,16 +191,15 @@ std::string ManagementServer::getStatConfigJSON()      return ss.str();  } -std::string ManagementServer::getValuesJSON() +std::string ManagementServer::get_input_values_json()  {      unique_lock<mutex> lock(m_statsmutex);      std::ostringstream ss;      ss << "{ \"values\" : {\n"; -    std::map<std::string,InputStat*>::iterator iter;      int i = 0; -    for(iter = m_inputStats.begin(); iter != m_inputStats.end(); +    for (auto iter = m_input_stats.begin(); iter != m_input_stats.end();              ++iter, i++)      {          const std::string& id = iter->first; @@ -220,6 +218,31 @@ std::string ManagementServer::getValuesJSON()      return ss.str();  } +std::string ManagementServer::get_output_values_json() +{ +    unique_lock<mutex> lock(m_statsmutex); + +    std::ostringstream ss; +    ss << "{ \"output_values\" : {\n"; + +    int i = 0; +    for (auto iter = m_output_stats.begin(); iter != m_output_stats.end(); +            ++iter, i++) +    { +        auto listen_port = iter->first; +        auto num_connections = iter->second; +        if (i > 0) { +            ss << " ,\n"; +        } +        ss << " \"edi_tcp_" << listen_port << "\" : { \"num_connections\": " << +            num_connections << "} "; +    } + +    ss << "}\n}\n"; + +    return ss.str(); +} +  ManagementServer::ManagementServer() :      m_zmq_context(),      m_zmq_sock(m_zmq_context, ZMQ_REP), @@ -323,10 +346,13 @@ void ManagementServer::handle_message(zmq::message_t& zmq_message)                  << "}\n";          }          else if (data == "config") { -            answer << getStatConfigJSON(); +            answer << get_input_config_json();          }          else if (data == "values") { -            answer << getValuesJSON(); +            answer << get_input_values_json(); +        } +        else if (data == "output_values") { +            answer << get_output_values_json();          }          else if (data == "getptree") {              unique_lock<mutex> lock(m_configmutex); @@ -366,12 +392,12 @@ InputStat::InputStat(const std::string& name) :  InputStat::~InputStat()  { -    get_mgmt_server().unregisterInput(m_name); +    get_mgmt_server().unregister_input(m_name);  }  void InputStat::registerAtServer()  { -    get_mgmt_server().registerInput(this); +    get_mgmt_server().register_input(this);  }  void InputStat::notifyBuffer(long bufsize) @@ -447,7 +473,7 @@ void InputStat::notifyPeakLevels(int peak_left, int peak_right)      }  } -void InputStat::notifyUnderrun(void) +void InputStat::notifyUnderrun()  {      unique_lock<mutex> lock(m_mutex); @@ -466,7 +492,7 @@ void InputStat::notifyUnderrun(void)      }  } -void InputStat::notifyOverrun(void) +void InputStat::notifyOverrun()  {      unique_lock<mutex> lock(m_mutex); @@ -612,11 +638,7 @@ input_state_t InputStat::determineState()      // STATE CALCULATION -    /* If the buffer has been empty for more than -     * INPUT_NODATA_TIMEOUT, we go to the NoData state. -     * -     * Consider an empty deque to be NoData too. -     */ +    /* Consider an empty deque to be NoData. */      if (std::all_of(                  m_buffer_fill_stats.begin(), m_buffer_fill_stats.end(),                  [](const fill_stat_t& fs) { return fs.bufsize == 0; }) ) { diff --git a/src/ManagementServer.h b/src/ManagementServer.h index 6e39922..93ad28c 100644 --- a/src/ManagementServer.h +++ b/src/ManagementServer.h @@ -2,7 +2,7 @@     Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2018 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      http://www.opendigitalradio.org @@ -64,7 +64,6 @@  #include <boost/property_tree/ptree.hpp>  #include <boost/property_tree/json_parser.hpp> -#include <cmath>  /*** State handing ***/  /* An input can be in one of the following three states: @@ -94,20 +93,20 @@ class InputStat          InputStat(const InputStat& other) = delete;          InputStat& operator=(const InputStat& other) = delete;          ~InputStat(); -        void registerAtServer(void); +        void registerAtServer(); -        std::string get_name(void) const { return m_name; } +        std::string get_name() const { return m_name; }          /* This function is called for every frame read by           * the multiplexer */          void notifyBuffer(long bufsize);          void notifyTimestampOffset(double offset);          void notifyPeakLevels(int peak_left, int peak_right); -        void notifyUnderrun(void); -        void notifyOverrun(void); +        void notifyUnderrun(); +        void notifyOverrun();          void notifyVersion(const std::string& version, uint32_t uptime_s); -        std::string encodeValuesJSON(void); -        input_state_t determineState(void); +        std::string encodeValuesJSON(); +        input_state_t determineState();      private:          std::string m_name; @@ -167,8 +166,10 @@ class ManagementServer          void open(int listenport);          /* Un-/Register a statistics data source */ -        void registerInput(InputStat* is); -        void unregisterInput(std::string id); +        void register_input(InputStat* is); +        void unregister_input(std::string id); + +        void update_edi_tcp_output_stat(uint16_t listen_port, size_t num_connections);          /* Load a ptree given by the management server.           * @@ -182,7 +183,7 @@ class ManagementServer          void update_ptree(const boost::property_tree::ptree& pt);          bool fault_detected() const { return m_fault; } -        void restart(void); +        void restart();      private:          void restart_thread(long); @@ -191,7 +192,7 @@ class ManagementServer          zmq::context_t m_zmq_context;          zmq::socket_t  m_zmq_sock; -        void serverThread(void); +        void serverThread();          void handle_message(zmq::message_t& zmq_message);          bool isInputRegistered(std::string& id); @@ -205,20 +206,25 @@ class ManagementServer          std::thread m_restarter_thread;          /******* Statistics Data ********/ -        std::map<std::string, InputStat*> m_inputStats; +        std::map<std::string, InputStat*> m_input_stats; + +        // Holds information about EDI/TCP outputs +        std::map<uint16_t /* port */, size_t /* num_connections */> m_output_stats;          /* Return a description of the configuration that will           * allow to define what graphs to be created           *           * returns: a JSON encoded configuration           */ -        std::string getStatConfigJSON(); +        std::string get_input_config_json();          /* Return the values for the statistics as defined in the configuration           *           * returns: JSON encoded statistics           */ -        std::string getValuesJSON(); +        std::string get_input_values_json(); + +        std::string get_output_values_json();          // mutex for accessing the map          std::mutex m_statsmutex; diff --git a/src/MuxElements.cpp b/src/MuxElements.cpp index d17b283..d9c5392 100644 --- a/src/MuxElements.cpp +++ b/src/MuxElements.cpp @@ -743,7 +743,15 @@ const json::map_t dabEnsemble::get_all_values() const      return map;  } -bool dabEnsemble::validate_linkage_sets() +bool dabEnsemble::validate_linkage_sets() const +{ +    unique_lock<mutex> lock(m_mutex); +    return validate_linkage_sets(services, m_linkagesets); +} + +bool dabEnsemble::validate_linkage_sets( +        const vec_sp_service& services, +        std::vector<std::shared_ptr<LinkageSet> > linkagesets)  {      for (const auto& ls : linkagesets) {          const std::string keyserviceuid = ls->keyservice; @@ -782,6 +790,41 @@ bool dabEnsemble::validate_linkage_sets()      return true;  } +std::vector<FrequencyInformation> dabEnsemble::get_frequency_information() const +{ +    unique_lock<mutex> lock(m_mutex); +    const auto fi = m_frequency_information; +    lock.unlock(); +    return fi; +} + +std::vector<std::shared_ptr<LinkageSet> > dabEnsemble::get_linkagesets() const +{ +    unique_lock<mutex> lock(m_mutex); +    const auto ls = m_linkagesets; +    lock.unlock(); +    return ls; +} + +std::vector<ServiceOtherEnsembleInfo> dabEnsemble::get_service_other_ensemble() const +{ +    unique_lock<mutex> lock(m_mutex); +    const auto oe = m_service_other_ensemble; +    lock.unlock(); +    return oe; +} + +void dabEnsemble::set_linking_config( +        std::vector<std::shared_ptr<LinkageSet> >& new_linkage_sets, +        std::vector<FrequencyInformation>& new_frequency_information, +        std::vector<ServiceOtherEnsembleInfo>& new_services_other_ensemble) +{ +    unique_lock<mutex> lock(m_mutex); +    m_frequency_information = new_frequency_information; +    m_linkagesets = new_linkage_sets; +    m_service_other_ensemble = new_services_other_ensemble; +} +  unsigned short DabSubchannel::getSizeCu() const  {      if (protection.form == UEP) { diff --git a/src/MuxElements.h b/src/MuxElements.h index d118df9..989730b 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -33,16 +33,13 @@  #include <memory>  #include <mutex>  #include <string> -#include <functional>  #include <exception> -#include <algorithm>  #include <chrono>  #include <optional>  #include <stdint.h>  #include "dabOutput/dabOutput.h"  #include "input/inputs.h"  #include "RemoteControl.h" -#include "Eti.h"  // Protection levels and bitrates for UEP.  const unsigned char ProtectionLevelTable[64] = { @@ -87,7 +84,7 @@ class MuxInitException : public std::exception          MuxInitException(const std::string m = "ODR-DabMux initialisation error")              throw()              : msg(m) {} -        ~MuxInitException(void) throw() {} +        ~MuxInitException() throw() {}          const char* what() const throw() { return msg.c_str(); }      private:          std::string msg; @@ -140,12 +137,12 @@ class AnnouncementCluster : public RemoteControllable {          uint16_t flags = 0;          std::string subchanneluid; -        std::string tostring(void) const; +        std::string tostring() const;          /* Check if the activation/deactivation timeout occurred,           * and return of if the Announcement is active           */ -        bool is_active(void); +        bool is_active();      private:          mutable std::mutex m_active_mutex; @@ -315,7 +312,10 @@ class dabEnsemble : public RemoteControllable {          virtual const json::map_t get_all_values() const;          /* Check if the Linkage Sets are valid */ -        bool validate_linkage_sets(void); +        bool validate_linkage_sets() const; +        static bool validate_linkage_sets( +                const vec_sp_service& services, +                std::vector<std::shared_ptr<LinkageSet> > linkagesets);          /* all fields are public, since this was a struct before */          uint16_t id = 0; @@ -350,9 +350,24 @@ class dabEnsemble : public RemoteControllable {          vec_sp_subchannel subchannels;          std::vector<std::shared_ptr<AnnouncementCluster> > clusters; -        std::vector<std::shared_ptr<LinkageSet> > linkagesets; -        std::vector<FrequencyInformation> frequency_information; -        std::vector<ServiceOtherEnsembleInfo> service_other_ensemble; + +        std::vector<FrequencyInformation> get_frequency_information() const; +        std::vector<std::shared_ptr<LinkageSet> > get_linkagesets() const; +        std::vector<ServiceOtherEnsembleInfo> get_service_other_ensemble() const; + +        void set_linking_config( +                std::vector<std::shared_ptr<LinkageSet> >& new_linkage_sets, +                std::vector<FrequencyInformation>& new_frequency_information, +                std::vector<ServiceOtherEnsembleInfo>& new_services_other_ensemble); + +    private: +        // The following can be updated by the RC while being read by the main +        // thread, and need to be protected +        std::vector<std::shared_ptr<LinkageSet> > m_linkagesets; +        std::vector<FrequencyInformation> m_frequency_information; +        std::vector<ServiceOtherEnsembleInfo> m_service_other_ensemble; + +        mutable std::mutex m_mutex;  }; @@ -372,7 +387,7 @@ struct dabProtectionEEP {      // select EEP profile A and B.      // Other values are for future use, see      // EN 300 401 Clause 6.2.1 "Basic sub-channel organisation" -    uint8_t GetOption(void) const { +    uint8_t GetOption() const {          return (this->profile == EEP_A) ? 0 : 1;      }  }; @@ -402,16 +417,16 @@ public:              protection() { }      // Calculate subchannel size in number of CU -    unsigned short getSizeCu(void) const; +    unsigned short getSizeCu() const;      // Calculate subchannel size in number of bytes -    unsigned short getSizeByte(void) const; +    unsigned short getSizeByte() const;      // Calculate subchannel size in number of uint32_t -    unsigned short getSizeWord(void) const; +    unsigned short getSizeWord() const;      // Calculate subchannel size in number of uint64_t -    unsigned short getSizeDWord(void) const; +    unsigned short getSizeDWord() const;      // Read from the input, using the correct buffer management      size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); @@ -574,7 +589,7 @@ class LinkageSet {                  bool hard,                  bool international); -        std::string get_name(void) const { return m_name; } +        std::string get_name() const { return m_name; }          std::list<ServiceLink> id_list; diff --git a/src/PcDebug.h b/src/PcDebug.h index d0b2b2c..68fceb8 100644 --- a/src/PcDebug.h +++ b/src/PcDebug.h @@ -19,8 +19,7 @@     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.     */ -#ifndef PC_DEBUG_ -#define PC_DEBUG_ +#pragma once  #ifdef HAVE_CONFIG_H  #   include "config.h" @@ -31,49 +30,28 @@  #include <stdio.h> -#define LOG	stderr +#define LOG stderr -#if !defined(_WIN32) || defined(__MINGW32__) -#  ifndef PDEBUG  -#    ifdef DEBUG  -#      define PDEBUG(fmt, args...) fprintf (LOG, fmt , ## args)  -#    else  -#      define PDEBUG(fmt, args...) -#    endif  -#  endif +#ifndef PDEBUG  #  ifdef DEBUG -#    define PDEBUG_VERBOSE(level, verbosity, fmt, args...) if (level <= verbosity)  { fprintf(LOG, fmt, ## args); fflush(LOG); } -#    define PDEBUG0_VERBOSE(level, verbosity, txt) if (level <= verbosity)  { fprintf(LOG, txt); fflush(LOG); } -#    define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) if (level <= verbosity)  { fprintf(LOG, txt, arg0); fflush(LOG); } -#    define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1); fflush(LOG); } -#    define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1, arg2); fflush(LOG); } -#    define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1, arg2, arg3); fflush(LOG); } -#  else -#    define PDEBUG_VERBOSE(level, verbosity, fmt, args...) -#    define PDEBUG0_VERBOSE(level, verbosity, txt) -#    define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) -#    define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) -#    define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) -#    define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) -#  endif // DEBUG -#else  // _WIN32 -#  ifdef _DEBUG -#    define PDEBUG -#    define PDEBUG_VERBOSE -#    define PDEBUG0_VERBOSE(level, verbosity, txt) if (level <= verbosity)  { fprintf(LOG, txt); fflush(LOG); } -#    define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) if (level <= verbosity)  { fprintf(LOG, txt, arg0); fflush(LOG); } -#    define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1); fflush(LOG); } -#    define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1, arg2); fflush(LOG); } -#    define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1, arg2, arg3); fflush(LOG); } +#    define PDEBUG(fmt, args...) fprintf (LOG, fmt , ## args)  #  else -#    define PDEBUG -#    define PDEBUG_VERBOSE -#    define PDEBUG0_VERBOSE(level, verbosity, txt) -#    define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) -#    define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) -#    define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) -#    define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) +#    define PDEBUG(fmt, args...)  #  endif  #endif +#ifdef DEBUG +#  define PDEBUG_VERBOSE(level, verbosity, fmt, args...) if (level <= verbosity)  { fprintf(LOG, fmt, ## args); fflush(LOG); } +#  define PDEBUG0_VERBOSE(level, verbosity, txt) if (level <= verbosity)  { fprintf(LOG, txt); fflush(LOG); } +#  define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) if (level <= verbosity)  { fprintf(LOG, txt, arg0); fflush(LOG); } +#  define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1); fflush(LOG); } +#  define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1, arg2); fflush(LOG); } +#  define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) if (level <= verbosity)  { fprintf(LOG, txt, arg0, arg1, arg2, arg3); fflush(LOG); } +#else +#  define PDEBUG_VERBOSE(level, verbosity, fmt, args...) +#  define PDEBUG0_VERBOSE(level, verbosity, txt) +#  define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) +#  define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) +#  define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) +#  define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) +#endif // DEBUG -#endif // PC_DEBUG_ diff --git a/src/fig/FIG.h b/src/fig/FIG.h index 9752245..eda4671 100644 --- a/src/fig/FIG.h +++ b/src/fig/FIG.h @@ -35,11 +35,19 @@ namespace FIC {  class FIGRuntimeInformation {      public: -        FIGRuntimeInformation(std::shared_ptr<dabEnsemble>& e) : + +        using dab_time_t = std::pair<uint32_t /* milliseconds */, time_t>; +        using get_time_func_t = std::function<dab_time_t()>; + +        FIGRuntimeInformation( +                std::shared_ptr<dabEnsemble>& e, +                get_time_func_t getTimeFunc) : +            getTimeFunc(getTimeFunc),              currentFrame(0),              ensemble(e),              factumAnalyzer(false) {} +        get_time_func_t getTimeFunc;          unsigned long currentFrame;          std::shared_ptr<dabEnsemble> ensemble;          bool factumAnalyzer; diff --git a/src/fig/FIG0_10.cpp b/src/fig/FIG0_10.cpp index 56ce9fb..240aa19 100644 --- a/src/fig/FIG0_10.cpp +++ b/src/fig/FIG0_10.cpp @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2016 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li     */  /* @@ -23,7 +23,6 @@     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.  */ -#include "fig/FIG0structs.h"  #include "fig/FIG0_10.h"  #include "utils.h" @@ -89,7 +88,7 @@ FillStatus FIG0_10::fill(uint8_t *buf, size_t max_size)          return fs;      } -    //Time and country identifier +    // Time and country identifier      auto fig0_10 = (FIGtype0_10_LongForm*)buf;      fig0_10->FIGtypeNumber = 0; @@ -102,9 +101,9 @@ FillStatus FIG0_10::fill(uint8_t *buf, size_t max_size)      remaining -= 2;      struct tm timeData; -    time_t dab_time_seconds = 0; -    uint32_t dab_time_millis = 0; -    get_dab_time(&dab_time_seconds, &dab_time_millis); +    const auto dab_time = m_rti->getTimeFunc(); +    time_t dab_time_seconds = dab_time.second; +    uint32_t dab_time_millis = dab_time.first;      gmtime_r(&dab_time_seconds, &timeData);      fig0_10->RFU = 0; diff --git a/src/fig/FIG0_21.cpp b/src/fig/FIG0_21.cpp index 5855fb1..ea6ebfe 100644 --- a/src/fig/FIG0_21.cpp +++ b/src/fig/FIG0_21.cpp @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2018 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li     */  /* @@ -119,11 +119,12 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)      auto ensemble = m_rti->ensemble;      if (not m_initialised) { -        freqInfoFIG0_21 = ensemble->frequency_information.begin(); +        m_freq_info = ensemble->get_frequency_information(); +        m_freq_info_it = m_freq_info.begin();          fi_frequency_index = 0; -        if (freqInfoFIG0_21 != ensemble->frequency_information.end()) { -            m_last_oe = freqInfoFIG0_21->other_ensemble; +        if (m_freq_info_it != m_freq_info.end()) { +            m_last_oe = m_freq_info_it->other_ensemble;          }          m_initialised = true;      } @@ -131,13 +132,13 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)      FIGtype0* fig0 = nullptr;      auto advance_loop = [&](void){ -            if (fi_frequency_index == get_num_frequencies(freqInfoFIG0_21)) { -                ++freqInfoFIG0_21; +            if (fi_frequency_index == get_num_frequencies(m_freq_info_it)) { +                ++m_freq_info_it;                  fi_frequency_index = 0;              }          }; -    for (; freqInfoFIG0_21 != ensemble->frequency_information.end(); +    for (; m_freq_info_it != m_freq_info.end();              advance_loop()) {          /* For better usage of FIC capacity, we want to transmit           * frequency lists with @@ -149,7 +150,7 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)          // Check we have space for one frequency          size_t required_fi_size = 2; // RegionId + length of FI list          size_t list_entry_size = sizeof(struct FIGtype0_21_fi_list_header); -        switch (freqInfoFIG0_21->rm) { +        switch (m_freq_info_it->rm) {              case RangeModulation::dab_ensemble:                  list_entry_size += 3;                  break; @@ -170,12 +171,12 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)          const size_t required_size =              sizeof(struct FIGtype0_21_header) + required_fi_size; -        if (m_last_oe != freqInfoFIG0_21->other_ensemble) { +        if (m_last_oe != m_freq_info_it->other_ensemble) {              // Trigger resend of FIG0 when OE changes              fig0 = nullptr; -            m_last_oe = freqInfoFIG0_21->other_ensemble; +            m_last_oe = m_freq_info_it->other_ensemble;              etiLog.level(FIG0_21_TRACE) << "FIG0_21::switch OE to " << -                freqInfoFIG0_21->other_ensemble; +                m_freq_info_it->other_ensemble;          }          if (fig0 == nullptr) { @@ -188,7 +189,7 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)              // Database start or continuation flag, EN 300 401 Clause 5.2.2.1 part b)              fig0->CN = (fi_frequency_index == 0 ? 0 : 1); -            fig0->OE = freqInfoFIG0_21->other_ensemble ? 1 : 0; +            fig0->OE = m_freq_info_it->other_ensemble ? 1 : 0;              fig0->PD = false;              fig0->Extension = 21; @@ -199,8 +200,8 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)              break;          } -        etiLog.level(FIG0_21_TRACE) << "FIG0_21::loop " << freqInfoFIG0_21->uid << " " << -            std::distance(ensemble->frequency_information.begin(), freqInfoFIG0_21) << +        etiLog.level(FIG0_21_TRACE) << "FIG0_21::loop " << m_freq_info_it->uid << " " << +            std::distance(m_freq_info.begin(), m_freq_info_it) <<              " freq entry " << fi_frequency_index;          auto *fig0_21_header = (FIGtype0_21_header*)buf; @@ -217,26 +218,26 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)          buf          += sizeof(FIGtype0_21_fi_list_header);          remaining    -= sizeof(FIGtype0_21_fi_list_header); -        fi_list_header->continuity = freqInfoFIG0_21->continuity; +        fi_list_header->continuity = m_freq_info_it->continuity;          fi_list_header->length_freq_list = 0; -        fi_list_header->range_modulation = static_cast<uint8_t>(freqInfoFIG0_21->rm); +        fi_list_header->range_modulation = static_cast<uint8_t>(m_freq_info_it->rm);          bool continue_loop = true; -        switch (freqInfoFIG0_21->rm) { +        switch (m_freq_info_it->rm) {              case RangeModulation::dab_ensemble: -                fi_list_header->setId(freqInfoFIG0_21->fi_dab.eid); +                fi_list_header->setId(m_freq_info_it->fi_dab.eid);                  for (size_t num_inserted = 0, i = fi_frequency_index;                          num_inserted < 2 and -                        i < freqInfoFIG0_21->fi_dab.frequencies.size(); +                        i < m_freq_info_it->fi_dab.frequencies.size();                          num_inserted++, i++) {                      if (remaining < 3) {                          continue_loop = false;                          break;                      } -                    const auto& freq = freqInfoFIG0_21->fi_dab.frequencies.at(i); +                    const auto& freq = m_freq_info_it->fi_dab.frequencies.at(i);                      auto *field = (FIGtype0_21_fi_dab_entry*)buf;                      field->control_field = static_cast<uint8_t>(freq.control_field); @@ -254,18 +255,18 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)                  }                  break;              case RangeModulation::fm_with_rds: -                fi_list_header->setId(freqInfoFIG0_21->fi_fm.pi_code); +                fi_list_header->setId(m_freq_info_it->fi_fm.pi_code);                  for (size_t num_inserted = 0, i = fi_frequency_index;                          num_inserted < 7 and -                        i < freqInfoFIG0_21->fi_fm.frequencies.size(); +                        i < m_freq_info_it->fi_fm.frequencies.size();                          num_inserted++, i++) {                      if (remaining < 1) {                          continue_loop = false;                          break;                      } -                    const auto& freq = freqInfoFIG0_21->fi_fm.frequencies.at(i); +                    const auto& freq = m_freq_info_it->fi_fm.frequencies.at(i);                      // RealFreq = 87.5 MHz + (F * 100kHz)                      // => F = (RealFreq - 87.5 MHz) / 100kHz                      // Do the whole calculation in kHz: @@ -280,14 +281,14 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)                  }                  break;              case RangeModulation::drm: -                fi_list_header->setId((freqInfoFIG0_21->fi_drm.drm_service_id) & 0xFFFF); +                fi_list_header->setId((m_freq_info_it->fi_drm.drm_service_id) & 0xFFFF);                  if (remaining < 3) {                      throw logic_error("Incorrect DRM FI size calculation");                  }                  // Id field 2 -                *buf = (freqInfoFIG0_21->fi_drm.drm_service_id >> 16) & 0xFF; +                *buf = (m_freq_info_it->fi_drm.drm_service_id >> 16) & 0xFF;                  fig0_21_header->length_fi += 1;                  fi_list_header->addToLength(1); @@ -297,14 +298,14 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)                  for (size_t num_inserted = 0, i = fi_frequency_index;                          num_inserted < 3 and -                        i < freqInfoFIG0_21->fi_drm.frequencies.size(); +                        i < m_freq_info_it->fi_drm.frequencies.size();                          num_inserted++, i++) {                      if (remaining < 2) {                          continue_loop = false;                          break;                      } -                    const auto& freq = freqInfoFIG0_21->fi_drm.frequencies.at(i); +                    const auto& freq = m_freq_info_it->fi_drm.frequencies.at(i);                      uint16_t freq_field = static_cast<uint16_t>(freq * 1000.0f);                      buf[0] = freq_field >> 8;                      buf[1] = freq_field & 0xFF; @@ -318,14 +319,14 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)                  }                  break;              case RangeModulation::amss: -                fi_list_header->setId((freqInfoFIG0_21->fi_amss.amss_service_id) & 0xFFFF); +                fi_list_header->setId((m_freq_info_it->fi_amss.amss_service_id) & 0xFFFF);                  if (remaining < 3) {                      throw logic_error("Incorrect AMSS FI size calculation");                  }                  // Id field 2 -                *buf = (freqInfoFIG0_21->fi_amss.amss_service_id >> 16) & 0xFF; +                *buf = (m_freq_info_it->fi_amss.amss_service_id >> 16) & 0xFF;                  fig0_21_header->length_fi += 1;                  fi_list_header->addToLength(1); @@ -335,14 +336,14 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)                  for (size_t num_inserted = 0, i = fi_frequency_index;                          num_inserted < 3 and -                        i < freqInfoFIG0_21->fi_amss.frequencies.size(); +                        i < m_freq_info_it->fi_amss.frequencies.size();                          num_inserted++, i++) {                      if (remaining < 2) {                          continue_loop = false;                          break;                      } -                    const auto& freq = freqInfoFIG0_21->fi_amss.frequencies.at(i); +                    const auto& freq = m_freq_info_it->fi_amss.frequencies.at(i);                      uint16_t freq_field = static_cast<uint16_t>(freq * 1000.0f);                      buf[0] = freq_field >> 8;                      buf[1] = freq_field & 0xFF; @@ -370,10 +371,10 @@ FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size)          }      } // for over FI -    if (freqInfoFIG0_21 == ensemble->frequency_information.end()) { +    if (m_freq_info_it == m_freq_info.end()) {          fs.complete_fig_transmitted = true; -        freqInfoFIG0_21 = ensemble->frequency_information.begin(); +        m_freq_info_it = m_freq_info.begin();          fi_frequency_index = 0;      } diff --git a/src/fig/FIG0_21.h b/src/fig/FIG0_21.h index 706dead..4e56c8c 100644 --- a/src/fig/FIG0_21.h +++ b/src/fig/FIG0_21.h @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2018 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li     */  /* @@ -27,7 +27,6 @@  #include <cstdint>  #include <vector> -#include <memory>  namespace FIC { @@ -49,7 +48,8 @@ class FIG0_21 : public IFIG          bool m_initialised = false;          bool m_last_oe = false; -        std::vector<FrequencyInformation>::iterator freqInfoFIG0_21; +        std::vector<FrequencyInformation> m_freq_info; +        std::vector<FrequencyInformation>::iterator m_freq_info_it;          size_t fi_frequency_index = 0;  }; diff --git a/src/fig/FIG0_24.cpp b/src/fig/FIG0_24.cpp index c48d8de..c254e63 100644 --- a/src/fig/FIG0_24.cpp +++ b/src/fig/FIG0_24.cpp @@ -73,21 +73,22 @@ FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size)          " ********************************";      if (not m_initialised) { -        serviceFIG0_24 = ensemble->service_other_ensemble.begin(); +        m_services = ensemble->get_service_other_ensemble(); +        m_services_it = m_services.begin();          m_initialised = true;      } -    const auto last_service = ensemble->service_other_ensemble.end(); +    const auto last_service = m_services.end();      bool last_oe = false;      // Rotate through the subchannels until there is no more      // space -    for (; serviceFIG0_24 != last_service; ++serviceFIG0_24) { +    for (; m_services_it != last_service; ++m_services_it) {          shared_ptr<DabService> service;          for (const auto& local_service : ensemble->services) { -            if (local_service->id == serviceFIG0_24->service_id) { +            if (local_service->id == m_services_it->service_id) {                  service = local_service;                  break;              } @@ -105,7 +106,7 @@ FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size)          etiLog.log(FIG0_24_TRACE, "FIG0_24::fill  loop OE=%d SId=%04x %s/%s",                  oe, -                serviceFIG0_24->service_id, +                m_services_it->service_id,                  m_inserting_audio_not_data ? "AUDIO" : "DATA",                  type == subchannel_type_t::DABAudio ? "Audio" :                  type == subchannel_type_t::Packet ? "Packet" : @@ -117,7 +118,7 @@ FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size)          const ssize_t required_size =              (isProgramme ? 2 : 4) + 1 + -            serviceFIG0_24->other_ensembles.size() * 2; +            m_services_it->other_ensembles.size() * 2;          if (fig0 == nullptr) { @@ -132,7 +133,7 @@ FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size)              fig0->FIGtypeNumber = 0;              fig0->Length = 1;              // CN according to ETSI TS 103 176, Clause 5.3.4.1 -            bool isFirst = serviceFIG0_24 == ensemble->service_other_ensemble.begin(); +            bool isFirst = m_services_it == m_services.begin();              fig0->CN = (isFirst ? 0 : 1);              fig0->OE = oe;              fig0->PD = isProgramme ? 0 : 1; @@ -149,33 +150,33 @@ FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size)          if (isProgramme) {              auto fig0_24_audioservice = (FIGtype0_24_audioservice*)buf; -            fig0_24_audioservice->SId = htons(serviceFIG0_24->service_id); +            fig0_24_audioservice->SId = htons(m_services_it->service_id);              fig0_24_audioservice->rfa = 0;              fig0_24_audioservice->CAId = 0; -            fig0_24_audioservice->Length = serviceFIG0_24->other_ensembles.size(); +            fig0_24_audioservice->Length = m_services_it->other_ensembles.size();              buf += 3;              fig0->Length += 3;              remaining -= 3;              etiLog.log(FIG0_24_TRACE, "FIG0_24::fill  audio SId=%04x", -               serviceFIG0_24->service_id); +               m_services_it->service_id);          }          else {              auto fig0_24_dataservice = (FIGtype0_24_dataservice*)buf; -            fig0_24_dataservice->SId = htonl(serviceFIG0_24->service_id); +            fig0_24_dataservice->SId = htonl(m_services_it->service_id);              fig0_24_dataservice->rfa = 0;              fig0_24_dataservice->CAId = 0; -            fig0_24_dataservice->Length = serviceFIG0_24->other_ensembles.size(); +            fig0_24_dataservice->Length = m_services_it->other_ensembles.size();              buf += 4;              fig0->Length += 4;              remaining -= 4;              etiLog.log(FIG0_24_TRACE, "FIG0_24::fill  data SId=%04x", -               serviceFIG0_24->service_id); +               m_services_it->service_id);          } -        for (const uint16_t oe : serviceFIG0_24->other_ensembles) { +        for (const uint16_t oe : m_services_it->other_ensembles) {              buf[0] = oe >> 8;              buf[1] = oe & 0xFF; @@ -185,7 +186,7 @@ FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size)          }      } -    if (serviceFIG0_24 == last_service) { +    if (m_services_it == last_service) {          etiLog.log(FIG0_24_TRACE, "FIG0_24::loop reached last");          fs.complete_fig_transmitted = true;          m_initialised = false; diff --git a/src/fig/FIG0_24.h b/src/fig/FIG0_24.h index d1e7604..7677e16 100644 --- a/src/fig/FIG0_24.h +++ b/src/fig/FIG0_24.h @@ -47,7 +47,8 @@ class FIG0_24 : public IFIG          FIGRuntimeInformation *m_rti;          bool m_initialised;          bool m_inserting_audio_not_data; -        std::vector<ServiceOtherEnsembleInfo>::iterator serviceFIG0_24; +        std::vector<ServiceOtherEnsembleInfo> m_services; +        std::vector<ServiceOtherEnsembleInfo>::iterator m_services_it;  };  } diff --git a/src/fig/FIG0_6.cpp b/src/fig/FIG0_6.cpp index 61d950d..e9e6220 100644 --- a/src/fig/FIG0_6.cpp +++ b/src/fig/FIG0_6.cpp @@ -256,7 +256,7 @@ void FIG0_6::update()      // TODO check if AMSS and DRM have to be put into a single subset -    for (const auto& linkageset : m_rti->ensemble->linkagesets) { +    for (const auto& linkageset : m_rti->ensemble->get_linkagesets()) {          const auto lsn = linkageset->lsn;          if (linkageset->keyservice.empty()) { diff --git a/src/fig/FIG0_6.h b/src/fig/FIG0_6.h index 770c4d5..96464d2 100644 --- a/src/fig/FIG0_6.h +++ b/src/fig/FIG0_6.h @@ -26,8 +26,6 @@  #pragma once  #include <cstdint> -#include <vector> -#include <memory>  namespace FIC { diff --git a/src/fig/FIG0structs.h b/src/fig/FIG0structs.h index 5f514b3..2e107e8 100644 --- a/src/fig/FIG0structs.h +++ b/src/fig/FIG0structs.h @@ -24,19 +24,17 @@  */  #pragma once -  #include <cstdint> -  #include "fig/FIG.h" -#define FIG0_13_APPTYPE_SLIDESHOW  0x2 -#define FIG0_13_APPTYPE_WEBSITE    0x3 -#define FIG0_13_APPTYPE_TPEG       0x4 -#define FIG0_13_APPTYPE_DGPS       0x5 -#define FIG0_13_APPTYPE_TMC        0x6 -#define FIG0_13_APPTYPE_SPI        0x7 -#define FIG0_13_APPTYPE_DABJAVA    0x8 -#define FIG0_13_APPTYPE_JOURNALINE 0x44a +constexpr uint16_t FIG0_13_APPTYPE_SLIDESHOW  = 0x2; +constexpr uint16_t FIG0_13_APPTYPE_WEBSITE    = 0x3; +constexpr uint16_t FIG0_13_APPTYPE_TPEG       = 0x4; +constexpr uint16_t FIG0_13_APPTYPE_DGPS       = 0x5; +constexpr uint16_t FIG0_13_APPTYPE_TMC        = 0x6; +constexpr uint16_t FIG0_13_APPTYPE_SPI        = 0x7; +constexpr uint16_t FIG0_13_APPTYPE_DABJAVA    = 0x8; +constexpr uint16_t FIG0_13_APPTYPE_JOURNALINE = 0x44a;  struct FIGtype0 { diff --git a/src/fig/FIG1.h b/src/fig/FIG1.h index 0fedffe..fe36717 100644 --- a/src/fig/FIG1.h +++ b/src/fig/FIG1.h @@ -23,8 +23,7 @@     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.  */ -#ifndef __FIG1_H_ -#define __FIG1_H_ +#pragma once  #include <cstdint> @@ -103,10 +102,6 @@ class FIG1_5 : public IFIG          vec_sp_service::iterator service;  }; -#ifdef _WIN32 -#   pragma pack(push) -#endif -  struct FIGtype1_0 {      uint8_t Length:5;      uint8_t FIGtypeNumber:3; @@ -165,11 +160,5 @@ struct FIGtype1_4_data {  } PACKED; -#ifdef _WIN32 -#   pragma pack(pop) -#endif -  } // namespace FIC -#endif // __FIG1_H_ - diff --git a/src/fig/FIG2.h b/src/fig/FIG2.h index ee3fed9..e69c5db 100644 --- a/src/fig/FIG2.h +++ b/src/fig/FIG2.h @@ -22,9 +22,7 @@     You should have received a copy of the GNU General Public License     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.  */ - -#ifndef __FIG2_H_ -#define __FIG2_H_ +#pragma once  #include <cstdint>  #include <map> @@ -117,10 +115,6 @@ class FIG2_4 : public IFIG          std::map<std::pair<uint32_t, uint8_t>, FIG2_Segments> segment_per_component;  }; -#ifdef _WIN32 -#   pragma pack(push) -#endif -  struct FIGtype2 {      uint8_t Length:5;      uint8_t FIGtypeNumber:3; @@ -159,11 +153,5 @@ struct FIG2_Extended_Label_WithTextControl {      uint8_t EncodingFlag:1;  } PACKED; -#ifdef _WIN32 -#   pragma pack(pop) -#endif -  } // namespace FIC -#endif // __FIG2_H_ - diff --git a/src/fig/FIGCarousel.cpp b/src/fig/FIGCarousel.cpp index 9748dbf..ceda275 100644 --- a/src/fig/FIGCarousel.cpp +++ b/src/fig/FIGCarousel.cpp @@ -68,8 +68,11 @@ bool FIGCarouselElement::check_deadline()  /**************** FIGCarousel *****************/ -FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) : -    m_rti(ensemble), +FIGCarousel::FIGCarousel( +        std::shared_ptr<dabEnsemble> ensemble, +        FIGRuntimeInformation::get_time_func_t getTimeFunc +        ) : +    m_rti(ensemble, getTimeFunc),      m_fig0_0(&m_rti),      m_fig0_1(&m_rti),      m_fig0_2(&m_rti), diff --git a/src/fig/FIGCarousel.h b/src/fig/FIGCarousel.h index 1e33577..a2a8022 100644 --- a/src/fig/FIGCarousel.h +++ b/src/fig/FIGCarousel.h @@ -67,7 +67,9 @@ enum class FIBAllocation {  class FIGCarousel {      public: -        FIGCarousel(std::shared_ptr<dabEnsemble> ensemble); +        FIGCarousel( +                std::shared_ptr<dabEnsemble> ensemble, +                FIGRuntimeInformation::get_time_func_t getTimeFunc);          /* Write all FIBs to the buffer, including correct padding and crc.           * Returns number of bytes written. diff --git a/src/input/Edi.cpp b/src/input/Edi.cpp index 141641f..b100f32 100644 --- a/src/input/Edi.cpp +++ b/src/input/Edi.cpp @@ -80,6 +80,7 @@ Edi::~Edi() {  void Edi::open(const std::string& name)  {      const std::regex re_udp("udp://:([0-9]+)"); +    const std::regex re_udp_bindto("udp://([^:]+):([0-9]+)");      const std::regex re_udp_multicast("udp://@([0-9.]+):([0-9]+)");      const std::regex re_udp_multicast_bindto("udp://([0-9.])+@([0-9.]+):([0-9]+)");      const std::regex re_tcp("tcp://(.*):([0-9]+)"); @@ -98,6 +99,12 @@ void Edi::open(const std::string& name)          m_udp_sock.reinit(udp_port);          m_udp_sock.setBlocking(false);      } +    else if (std::regex_match(name, m, re_udp_bindto)) { +        const int udp_port = std::stoi(m[2].str()); +        m_input_used = InputUsed::UDP; +        m_udp_sock.reinit(udp_port, m[1].str()); +        m_udp_sock.setBlocking(false); +    }      else if (std::regex_match(name, m, re_udp_multicast_bindto)) {          const string bind_to = m[1].str();          const string multicast_address = m[2].str(); diff --git a/src/input/File.cpp b/src/input/File.cpp index d9fe02a..c70feee 100644 --- a/src/input/File.cpp +++ b/src/input/File.cpp @@ -28,9 +28,6 @@  #include <cstdio>  #include <fcntl.h>  #include <unistd.h> -#ifndef _WIN32 -#   define O_BINARY 0 -#endif  #include "input/File.h"  #include "mpeg.h"  #include "ReedSolomon.h" @@ -39,9 +36,6 @@ using namespace std;  namespace Inputs { -#ifdef _WIN32 -#   pragma pack(push, 1) -#endif  struct packetHeader {      unsigned char addressHigh:2;      unsigned char last:1; @@ -52,11 +46,7 @@ struct packetHeader {      unsigned char dataLength:7;      unsigned char command;  } -#ifdef _WIN32 -#   pragma pack(pop) -#else  __attribute((packed)) -#endif  ; @@ -68,7 +58,7 @@ void FileBase::open(const std::string& name)          load_entire_file();      }      else { -        int flags = O_RDONLY | O_BINARY; +        int flags = O_RDONLY;          if (m_nonblock) {              flags |= O_NONBLOCK;          } @@ -140,13 +130,13 @@ ssize_t FileBase::load_entire_file()  {      // Clear the buffer if the file open fails, this allows user to stop transmission      // of the current data. -    vector<uint8_t> old_file_contents = move(m_file_contents); +    vector<uint8_t> old_file_contents = std::move(m_file_contents);      m_file_contents.clear();      m_file_contents_offset = 0;      // Read entire file in chunks of 4MiB      constexpr size_t blocksize = 4 * 1024 * 1024; -    constexpr int flags = O_RDONLY | O_BINARY; +    constexpr int flags = O_RDONLY;      m_fd = ::open(m_filename.c_str(), flags);      if (m_fd == -1) {          if (not m_file_open_alert_shown) { @@ -225,7 +215,7 @@ ssize_t FileBase::readFromFile(uint8_t *buffer, size_t size)              vector<uint8_t> remaining_buf;              copy(m_nonblock_buffer.begin() + size, m_nonblock_buffer.end(), back_inserter(remaining_buf)); -            m_nonblock_buffer = move(remaining_buf); +            m_nonblock_buffer = std::move(remaining_buf);              return size;          } @@ -18,23 +18,13 @@     You should have received a copy of the GNU General Public License     along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.     */ - -#ifndef _MPEG -#define _MPEG +#pragma once  #ifdef HAVE_CONFIG_H  #   include "config.h"  #endif -#ifdef _WIN32 -#   include <stddef.h> -#   include <basetsd.h> -#   include <io.h> - -#   define ssize_t SSIZE_T -#else -#   include <unistd.h> -#endif +#include <unistd.h>  #ifdef __cplusplus  extern "C" { @@ -86,4 +76,3 @@ int checkDabMpegFrame(void* data);  }  #endif -#endif // _MPEG diff --git a/src/test_statsserver.sh b/src/test_statsserver.sh index b240cf9..b475945 100755 --- a/src/test_statsserver.sh +++ b/src/test_statsserver.sh @@ -1 +1 @@ -clang++ -Wall --include=../config.h  StatsServer.cpp TestStatsServer.cpp Log.cpp -lboost_system -lboost_thread -o test && ./test +clang++ -Wall --include=../config.h  StatsServer.cpp TestStatsServer.cpp Log.cpp -lboost_thread -o test && ./test diff --git a/src/utils.cpp b/src/utils.cpp index e7ef224..63ad32c 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2021 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      http://www.opendigitalradio.org @@ -29,36 +29,13 @@  #include <iostream>  #include <memory>  #include <boost/algorithm/string/join.hpp> -#include "DabMux.h"  #include "utils.h"  #include "fig/FIG0structs.h"  using namespace std; -static time_t dab_time_seconds = 0; -static int dab_time_millis = 0; -  static void printServices(const vector<shared_ptr<DabService> >& services); -void update_dab_time() -{ -    if (dab_time_seconds == 0) { -        dab_time_seconds = time(nullptr); -    } else { -        dab_time_millis+= 24; -        if (dab_time_millis >= 1000) { -            dab_time_millis -= 1000; -            ++dab_time_seconds; -        } -    } -} - -void get_dab_time(time_t *time, uint32_t *millis) -{ -    *time = dab_time_seconds; -    *millis = dab_time_millis; -} -  uint32_t gregorian2mjd(int year, int month, int day)  { @@ -194,8 +171,6 @@ void printOutputs(const vector<shared_ptr<DabOutput> >& outputs)  void printServices(const vector<shared_ptr<DabService> >& services)  { -    int index = 0; -      etiLog.log(info, "--- Services list ---");      for (auto service : services) { @@ -223,7 +198,6 @@ void printServices(const vector<shared_ptr<DabService> >& services)              clusters_s.push_back(std::to_string(cluster));          }          etiLog.level(info) << " clusters: " << boost::join(clusters_s, ","); -        ++index;      }  } @@ -302,8 +276,6 @@ void printComponent(const shared_ptr<DabComponent>& component, const std::shared  void printSubchannels(const vec_sp_subchannel& subchannels)  { -    int index = 0; -      int total_num_cu = 0;      etiLog.log(info, "--- Subchannels list ---"); @@ -353,7 +325,6 @@ void printSubchannels(const vec_sp_subchannel& subchannels)          etiLog.log(info, " size (CU):  %i",                  subchannel->getSizeCu());          total_num_cu += subchannel->getSizeCu(); -        ++index;      }      etiLog.log(info, "Total ensemble size (CU):  %i", total_num_cu); @@ -361,12 +332,12 @@ void printSubchannels(const vec_sp_subchannel& subchannels)  static void printLinking(const shared_ptr<dabEnsemble>& ensemble)  { +    const auto linkagesets = ensemble->get_linkagesets();      etiLog.log(info, " Linkage Sets"); -    if (ensemble->linkagesets.empty()) { +    if (linkagesets.empty()) {          etiLog.level(info) << "  None ";      } -    for (const auto& ls : ensemble->linkagesets) { - +    for (const auto& ls : linkagesets) {          etiLog.level(info) << "  set " << ls->get_name();          etiLog.log(info,      "   LSN 0x%04x", ls->lsn);          etiLog.level(info) << "   active " << (ls->active ? "true" : "false"); @@ -398,11 +369,12 @@ static void printLinking(const shared_ptr<dabEnsemble>& ensemble)      }      etiLog.log(info, " Services in other ensembles"); -    if (ensemble->service_other_ensemble.empty()) { +    const auto service_other_ensemble = ensemble->get_service_other_ensemble(); +    if (service_other_ensemble.empty()) {          etiLog.level(info) << "  None ";      } -    for (const auto& s_oe : ensemble->service_other_ensemble) { +    for (const auto& s_oe : service_other_ensemble) {          int oe = 1;          for (const auto& local_service : ensemble->services) { @@ -422,11 +394,12 @@ static void printLinking(const shared_ptr<dabEnsemble>& ensemble)  static void printFrequencyInformation(const shared_ptr<dabEnsemble>& ensemble)  { +    const auto frequency_information = ensemble->get_frequency_information();      etiLog.log(info, " Frequency Information"); -    if (ensemble->frequency_information.empty()) { +    if (frequency_information.empty()) {          etiLog.level(info) << "  None ";      } -    for (const auto& fi : ensemble->frequency_information) { +    for (const auto& fi : frequency_information) {          etiLog.level(info) << "  FI " << fi.uid;          etiLog.level(info) << "   OE=" << (fi.other_ensemble ? 1 : 0);          switch (fi.rm) { diff --git a/src/utils.h b/src/utils.h index 331a0b2..d037bb3 100644 --- a/src/utils.h +++ b/src/utils.h @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2020 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li     This file contains a set of utility functions that are used to show @@ -34,10 +34,6 @@  #include <memory>  #include "MuxElements.h" -/* Must be called once per ETI frame to update the time */ -void update_dab_time(void); -void get_dab_time(time_t *time, uint32_t *millis); -  /* Convert a date and time into the modified Julian date   * used in FIG 0/10   * | 
