diff options
Diffstat (limited to 'src/DabMultiplexer.cpp')
| -rw-r--r-- | src/DabMultiplexer.cpp | 289 |
1 files changed, 220 insertions, 69 deletions
diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index fe7b156..7a8ac97 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) 2020 + 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_linkagesets, "Write 1 to this parameter to trigger a reload of the linkage sets 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,29 +209,22 @@ void DabMultiplexer::prepare(bool require_tai_clock) throw MuxInitException(); } - /* Ensure edi_time and TIST represent current time. 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(); - m_edi_time = chrono::system_clock::to_time_t(chrono::time_point_cast<Sec>(now)); - 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"); - } - m_timestamp = 0; - while (offset >= chrono::milliseconds(24)) { - increment_timestamp(); - offset -= chrono::milliseconds(24); - } + 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; - // Try to load offset once + bool tist_enabled = m_config.pt.get("general.tist", false); + + 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; - bool tist_enabled = m_pt.get("general.tist", false); - m_tist_offset = m_pt.get<int>("general.tist_offset", 0); + etiLog.log(info, "Startup CIF Count %i with timestamp: %d + %f", + currentFrame, edi_time, + (timestamp & 0xFFFFFF) / 16384000.0); + + // Try to load offset once m_tai_clock_required = (tist_enabled and edi_conf.enabled()) or require_tai_clock; @@ -359,12 +462,30 @@ void DabMultiplexer::prepare_data_inputs() } } -void DabMultiplexer::increment_timestamp() +void DabMultiplexer::reload_linkagesets() { - 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()) { + const auto pt_linking = new_conf.pt.get_child_optional("linking"); + std::vector<std::shared_ptr<LinkageSet> > linkagesets; + parse_linkage(pt_linking, linkagesets); + + etiLog.level(info) << "Validating " << linkagesets.size() << " new linkage sets."; + + if (ensemble->validate_linkage_sets(ensemble->services, linkagesets)) { + ensemble->linkagesets = linkagesets; + etiLog.level(info) << "Loaded new linkage sets."; + } + else { + etiLog.level(warn) << "New linkage set validation failed"; + } + } + } + catch (const std::exception& e) + { + etiLog.level(warn) << "Failed to update linkage sets: " << e.what(); } } @@ -381,12 +502,12 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs // For EDI, save ETI(LI) Management data into a TAG Item DETI edi::TagDETI edi_tagDETI; edi::TagStarPTR edi_tagStarPtr("DETI"); - map<DabSubchannel*, edi::TagESTn> edi_subchannelToTag; + vector<edi::TagESTn> edi_est_tags; // 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) { @@ -397,9 +518,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); @@ -415,8 +541,12 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs //****** Field FSYNC *****// // See ETS 300 799, 6.2.1.2 - sync ^= 0xffffff; - etiSync->FSYNC = sync; + if ((currentFrame & 1) == 0) { + etiSync->FSYNC = ETI_FSYNC1; + } + else { + etiSync->FSYNC = ETI_FSYNC1 ^ 0xffffff; + } /********************************************************************** *********** Section LIDATA of ETI(NI, G703) ********************** @@ -429,7 +559,6 @@ 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 = currentFrame % 250; edi_tagDETI.dlfc = currentFrame % 5000; @@ -510,7 +639,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs tag_ESTn.mst_data = nullptr; assert(subchannel->getSizeByte() % 8 == 0); - edi_subchannelToTag[subchannel.get()] = tag_ESTn; + edi_est_tags.push_back(std::move(tag_ESTn)); index += 4; } @@ -522,14 +651,9 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs eoh->MNSC = 0; - if (fc->FP == 0) { - // update the latched time only when FP==0 to ensure MNSC encodes - // a consistent time - m_edi_time_latched_for_mnsc = edi_time; - } - struct tm time_tm; - gmtime_r(&m_edi_time_latched_for_mnsc, &time_tm); + gmtime_r(&m_time.mnsc_time, &time_tm); + switch (fc->FP & 0x3) { case 0: { @@ -539,6 +663,12 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs mnsc->identifier = 0; mnsc->rfa = 0; } + + if (m_time.mnsc_increment_time) + { + m_time.mnsc_increment_time = false; + m_time.mnsc_time += 1; + } break; case 1: { @@ -580,21 +710,21 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs edi_tagDETI.fic_length = FICL * 4; // Insert all FIBs - fig_carousel.update(currentFrame); const bool fib3_present = (ensemble->transmission_mode == TransmissionMode_e::TM_III); - index += fig_carousel.write_fibs(&etiFrame[index], currentFrame % 4, fib3_present); + index += fig_carousel.write_fibs(&etiFrame[index], currentFrame, fib3_present); /********************************************************************** ****** Input Data Reading ******************************************* **********************************************************************/ - for (auto subchannel : ensemble->subchannels) { - edi::TagESTn& tag = edi_subchannelToTag[subchannel.get()]; + for (size_t i = 0; i < ensemble->subchannels.size(); i++) { + auto& subchannel = ensemble->subchannels[i]; 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, @@ -603,7 +733,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs } // save pointer to Audio or Data Stream into correct TagESTn for EDI - tag.mst_data = &etiFrame[index]; + edi_est_tags[i].mst_data = &etiFrame[index]; index += sizeSubchannel; } @@ -637,10 +767,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; @@ -677,8 +807,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 ***************************************** @@ -715,26 +844,32 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs edi_tagpacket.tag_items.push_back(&edi_tagStarPtr); edi_tagpacket.tag_items.push_back(&edi_tagDETI); - for (auto& tag : edi_subchannelToTag) { - edi_tagpacket.tag_items.push_back(&tag.second); + for (auto& tag : edi_est_tags) { + edi_tagpacket.tag_items.push_back(&tag); } 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 /********************************************************************** *********** Output a small message ********************************* **********************************************************************/ - if (currentFrame % 100 == 0) { + if (m_currentFrame % 100 == 0) { if (enableTist) { etiLog.log(info, "ETI frame number %i Timestamp: %d + %f", - currentFrame, edi_time, - (m_timestamp & 0xFFFFFF) / 16384000.0); + m_currentFrame, edi_time, + (timestamp & 0xFFFFFF) / 16384000.0); } else { etiLog.log(info, "ETI frame number %i Time: %d, no TIST", - currentFrame, edi_time); + m_currentFrame, edi_time); } } #endif @@ -760,7 +895,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_linkagesets") { + reload_linkagesets(); } else { stringstream ss; @@ -778,7 +916,12 @@ const std::string DabMultiplexer::get_parameter(const std::string& parameter) co ss << currentFrame; } else if (parameter == "tist_offset") { - ss << m_tist_offset; + ss << m_time.tist_offset(); + } + else if (parameter == "reload_linkagesets") { + ss << "Parameter '" << parameter << + "' is not write-only in controllable " << get_rc_name(); + throw ParameterError(ss.str()); } else { ss << "Parameter '" << parameter << @@ -789,3 +932,11 @@ 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 = currentFrame; + map["tist_offset"].v = m_time.tist_offset(); + return map; +} + |
