diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ConfigParser.cpp | 10 | ||||
-rw-r--r-- | src/DabMultiplexer.cpp | 54 | ||||
-rw-r--r-- | src/DabMultiplexer.h | 1 | ||||
-rw-r--r-- | src/MuxElements.cpp | 61 | ||||
-rw-r--r-- | src/MuxElements.h | 80 | ||||
-rw-r--r-- | src/dabOutput/edi/TagItems.cpp | 10 | ||||
-rw-r--r-- | src/dabOutput/edi/TagItems.h | 8 | ||||
-rw-r--r-- | src/fig/FIG0_10.cpp | 16 | ||||
-rw-r--r-- | src/fig/FIG0_9.cpp | 5 | ||||
-rw-r--r-- | src/fig/FIG1.cpp | 27 | ||||
-rw-r--r-- | src/fig/FIG1.h | 2 | ||||
-rw-r--r-- | src/fig/FIG2.cpp | 406 | ||||
-rw-r--r-- | src/fig/FIG2.h | 163 | ||||
-rw-r--r-- | src/fig/FIGCarousel.cpp | 11 | ||||
-rw-r--r-- | src/fig/FIGCarousel.h | 5 | ||||
-rw-r--r-- | src/utils.cpp | 34 |
16 files changed, 759 insertions, 134 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 0bf3070..62d81c7 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -412,7 +412,7 @@ static void parse_general(ptree& pt, } int success = -5; - string ensemble_label = pt_ensemble.get<string>("label"); + string ensemble_label = pt_ensemble.get<string>("label", ""); string ensemble_short_label(ensemble_label); try { ensemble_short_label = pt_ensemble.get<string>("shortlabel"); @@ -447,6 +447,8 @@ static void parse_general(ptree& pt, abort(); } + ensemble->label.setFIG2Label(pt_ensemble.get<string>("fig2_label", "")); + try { ptree pt_announcements = pt_ensemble.get_child("announcements"); for (auto announcement : pt_announcements) { @@ -549,7 +551,7 @@ void parse_ptree( int success = -5; - string servicelabel = pt_service.get<string>("label"); + string servicelabel = pt_service.get<string>("label", ""); string serviceshortlabel(servicelabel); try { serviceshortlabel = pt_service.get<string>("shortlabel"); @@ -584,6 +586,8 @@ void parse_ptree( abort(); } + service->label.setFIG2Label(pt_service.get<string>("fig2_label", "")); + service->id = new_service_id; service->ecc = hexparse(pt_service.get("ecc", "0")); service->pty_settings.pty = hexparse(pt_service.get("pty", "0")); @@ -744,6 +748,8 @@ void parse_ptree( abort(); } + component->label.setFIG2Label(pt_comp.get<string>("fig2_label", "")); + if (component->SCIdS == 0 and not component->label.long_label().empty()) { etiLog.level(warn) << "Primary component " << component->uid << " has label set. Since V2.1.1 of the specification, only secondary" diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index e84b58c..f56a4ff 100644 --- a/src/DabMultiplexer.cpp +++ b/src/DabMultiplexer.cpp @@ -92,8 +92,10 @@ DabMultiplexer::DabMultiplexer( m_clock_tai(split_pipe_separated_string(pt.get("general.tai_clock_bulletins", ""))), fig_carousel(ensemble) { - RC_ADD_PARAMETER(frames, - "Show number of frames generated [read-only]"); + gettimeofday(&mnsc_time, nullptr); + + RC_ADD_PARAMETER(frames, "Show number of frames generated [read-only]"); + RC_ADD_PARAMETER(tist_edioffset, "EDI Time offset in seconds"); rcs.enrol(&m_clock_tai); } @@ -169,7 +171,7 @@ void DabMultiplexer::prepare(bool require_tai_clock) throw MuxInitException(); } - auto last_subchannel = *(ensemble->subchannels.end() - 1); + const auto last_subchannel = *(ensemble->subchannels.end() - 1); if (last_subchannel->startAddress + last_subchannel->getSizeCu() > 864) { etiLog.log(error, "Total size in CU exceeds 864"); @@ -191,6 +193,7 @@ void DabMultiplexer::prepare(bool require_tai_clock) // Try to load offset once bool tist_enabled = m_pt.get("general.tist", false); + m_tist_edioffset = m_pt.get<int>("general.tist_edioffset", 0); m_tai_clock_required = (tist_enabled and edi_conf.enabled()) or require_tai_clock; @@ -198,7 +201,7 @@ void DabMultiplexer::prepare(bool require_tai_clock) try { m_clock_tai.get_offset(); } - catch (std::runtime_error& e) { + catch (const std::runtime_error& e) { etiLog.level(error) << "Could not initialise TAI clock properly. " "Do you have a working internet connection?"; @@ -400,9 +403,6 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs unsigned char etiFrame[6144]; unsigned short index = 0; - vec_sp_service::iterator service; - vec_sp_component::iterator component; - // FIC Length, DAB Mode I, II, IV -> FICL = 24, DAB Mode III -> FICL = 32 unsigned FICL = (ensemble->transmission_mode == TransmissionMode_e::TM_III ? 32 : 24); @@ -546,7 +546,8 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs eoh->MNSC = 0; - struct tm *time_tm = gmtime(&mnsc_time.tv_sec); + struct tm time_tm; + gmtime_r(&mnsc_time.tv_sec, &time_tm); switch (fc->FP & 0x3) { case 0: @@ -567,7 +568,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs case 1: { eti_MNSC_TIME_1 *mnsc = (eti_MNSC_TIME_1 *) &eoh->MNSC; - mnsc->setFromTime(time_tm); + mnsc->setFromTime(&time_tm); mnsc->accuracy = 1; mnsc->sync_to_frame = 1; } @@ -575,13 +576,13 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs case 2: { eti_MNSC_TIME_2 *mnsc = (eti_MNSC_TIME_2 *) &eoh->MNSC; - mnsc->setFromTime(time_tm); + mnsc->setFromTime(&time_tm); } break; case 3: { eti_MNSC_TIME_3 *mnsc = (eti_MNSC_TIME_3 *) &eoh->MNSC; - mnsc->setFromTime(time_tm); + mnsc->setFromTime(&time_tm); } break; } @@ -670,19 +671,18 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs edi_tagDETI.tsta = 0xffffff; } - edi_tagDETI.atstf = 1; - edi_tagDETI.utco = 0; - edi_tagDETI.seconds = 0; #if HAVE_OUTPUT_EDI - try { - bool tist_enabled = m_pt.get("general.tist", false); - - if (tist_enabled and m_tai_clock_required) { - edi_tagDETI.set_seconds(edi_time); + const bool tist_enabled = m_pt.get("general.tist", false); - // In case get_offset fails, we still want to update the EDI seconds + if (tist_enabled and m_tai_clock_required) { + try { const auto tai_utc_offset = m_clock_tai.get_offset(); - edi_tagDETI.set_tai_utc_offset(tai_utc_offset); + + edi_tagDETI.set_edi_time(edi_time + + std::chrono::seconds(m_tist_edioffset), + tai_utc_offset); + + edi_tagDETI.atstf = true; for (auto output : outputs) { shared_ptr<OutputMetadata> md_utco = @@ -698,9 +698,9 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs output->setMetadata(md_dlfc); } } - } - catch (std::runtime_error& e) { - etiLog.level(error) << "Could not get UTC-TAI offset for EDI timestamp"; + catch (const std::runtime_error& e) { + etiLog.level(error) << "Could not get UTC-TAI offset for EDI timestamp"; + } } #endif @@ -872,6 +872,9 @@ void DabMultiplexer::set_parameter(const std::string& parameter, " is read-only"; throw ParameterError(ss.str()); } + else if (parameter == "tist_edioffset") { + m_tist_edioffset = std::stoi(value); + } else { stringstream ss; ss << "Parameter '" << parameter << @@ -887,6 +890,9 @@ const std::string DabMultiplexer::get_parameter(const std::string& parameter) co if (parameter == "frames") { ss << currentFrame; } + else if (parameter == "tist_edioffset") { + ss << m_tist_edioffset; + } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); diff --git a/src/DabMultiplexer.h b/src/DabMultiplexer.h index 499e023..127ecfb 100644 --- a/src/DabMultiplexer.h +++ b/src/DabMultiplexer.h @@ -95,6 +95,7 @@ class DabMultiplexer : public RemoteControllable { std::shared_ptr<dabEnsemble> ensemble; + int m_tist_edioffset = 0; bool m_tai_clock_required; ClockTAI m_clock_tai; diff --git a/src/MuxElements.cpp b/src/MuxElements.cpp index dcd408c..44e097c 100644 --- a/src/MuxElements.cpp +++ b/src/MuxElements.cpp @@ -184,13 +184,13 @@ const string AnnouncementCluster::get_parameter(const string& parameter) const int DabLabel::setLabel(const std::string& label) { try { - auto ebu_label = charset_converter.convert(label, false); + auto ebu_label = charset_converter.utf8_to_ebu(label, false); size_t len = ebu_label.length(); if (len > DABLABEL_LENGTH) { return -3; } - m_label = ebu_label; + m_fig1_label = ebu_label; } catch (const utf8::exception& e) { etiLog.level(warn) << "Failed to convert label '" << label << @@ -201,10 +201,10 @@ int DabLabel::setLabel(const std::string& label) return -3; } - m_label = label; + m_fig1_label = label; } - m_flag = 0xFF00; // truncate the label to the eight first characters + m_fig1_flag = 0xFF00; // truncate the label to the eight first characters return 0; } @@ -212,23 +212,23 @@ int DabLabel::setLabel(const std::string& label) int DabLabel::setLabel(const std::string& label, const std::string& short_label) { DabLabel newlabel; - newlabel.m_flag = 0xFF00; + newlabel.m_fig1_flag = 0xFF00; try { - newlabel.m_label = charset_converter.convert(label, false); + newlabel.m_fig1_label = charset_converter.utf8_to_ebu(label, false); - size_t len = newlabel.m_label.length(); + size_t len = newlabel.m_fig1_label.length(); if (len > DABLABEL_LENGTH) { return -3; } - int flag = newlabel.setShortLabel( - charset_converter.convert(short_label, false)); + int flag = newlabel.setFIG1ShortLabel( + charset_converter.utf8_to_ebu(short_label, false)); if (flag < 0) { return flag; } - m_flag = flag & 0xFFFF; + m_fig1_flag = flag & 0xFFFF; } catch (const utf8::exception& e) { etiLog.level(warn) << "Failed to convert label '" << label << @@ -241,8 +241,8 @@ int DabLabel::setLabel(const std::string& label, const std::string& short_label) return -3; } - newlabel.m_label = label; - newlabel.m_flag = 0xFF00; + newlabel.m_fig1_label = label; + newlabel.m_fig1_flag = 0xFF00; int result = newlabel.setLabel(label); if (result < 0) { @@ -250,16 +250,16 @@ int DabLabel::setLabel(const std::string& label, const std::string& short_label) } /* First check if we can actually create the short label */ - int flag = newlabel.setShortLabel(short_label); + int flag = newlabel.setFIG1ShortLabel(short_label); if (flag < 0) { return flag; } - m_flag = flag & 0xFFFF; + m_fig1_flag = flag & 0xFFFF; } // short label is valid. - m_label = newlabel.m_label; + m_fig1_label = newlabel.m_fig1_label; return 0; } @@ -280,7 +280,7 @@ int DabLabel::setLabel(const std::string& label, const std::string& short_label) * -1 if the short_label is not a representable * -2 if the short_label is too long */ -int DabLabel::setShortLabel(const std::string& slabel) +int DabLabel::setFIG1ShortLabel(const std::string& slabel) { const char* slab = slabel.c_str(); uint16_t flag = 0x0; @@ -288,8 +288,8 @@ int DabLabel::setShortLabel(const std::string& slabel) /* Iterate over the label and set the bits in the flag * according to the characters in the slabel */ - for (size_t i = 0; i < m_label.size(); ++i) { - if (*slab == m_label[i]) { + for (size_t i = 0; i < m_fig1_label.size(); ++i) { + if (*slab == m_fig1_label[i]) { flag |= 0x8000 >> i; if (*(++slab) == '\0') { break; @@ -321,26 +321,37 @@ int DabLabel::setShortLabel(const std::string& slabel) const string DabLabel::long_label() const { - return charset_converter.convert_ebu_to_utf8(m_label); + return charset_converter.ebu_to_utf8(m_fig1_label); } const string DabLabel::short_label() const { stringstream shortlabel; - for (size_t i = 0; i < m_label.size(); ++i) { - if (m_flag & 0x8000 >> i) { - shortlabel << m_label[i]; + for (size_t i = 0; i < m_fig1_label.size(); ++i) { + if (m_fig1_flag & 0x8000 >> i) { + shortlabel << m_fig1_label[i]; } } - return charset_converter.convert_ebu_to_utf8(shortlabel.str()); + return charset_converter.ebu_to_utf8(shortlabel.str()); +} + +const string DabLabel::fig2_label() const +{ + return m_fig2_label; +} + +int DabLabel::setFIG2Label(const std::string& label) +{ + m_fig2_label = label; + return 0; } void DabLabel::writeLabel(uint8_t* buf) const { memset(buf, ' ', DABLABEL_LENGTH); - if (m_label.size() <= DABLABEL_LENGTH) { - std::copy(m_label.begin(), m_label.end(), (char*)buf); + if (m_fig1_label.size() <= DABLABEL_LENGTH) { + std::copy(m_fig1_label.begin(), m_fig1_label.end(), (char*)buf); } } diff --git a/src/MuxElements.h b/src/MuxElements.h index be10bf9..b2be73f 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -167,27 +167,50 @@ class DabLabel */ int setLabel(const std::string& label); + /* Set the FIG2 label. label must be UTF-8. + * + * returns: 0 on success + */ + int setFIG2Label(const std::string& label); + /* Write the label to the 16-byte buffer given in buf * In the DAB standard, the label is 16 bytes long, and is * padded using spaces. */ void writeLabel(uint8_t* buf) const; - uint16_t flag() const { return m_flag; } + // For FIG 1 + bool has_fig1_label() const { return not m_fig1_label.empty(); }; + uint16_t flag() const { return m_fig1_flag; } const std::string long_label() const; const std::string short_label() const; + // For FIG 2 + bool has_fig2_label() const { return not m_fig2_label.empty(); }; + const std::string fig2_label() const; + + /* FIG 2 labels are either in UCS-2 or in UTF-8. Because there are upcoming + * changes in the spec regarding the encoding of FIG2 (currently in draft + * ETSI TS 103 176 v2.2.1), the character flag is not implemented yet. + * + * Both FIG 1 and FIG 2 labels can be sent, and receiver will show the one + * they support. + */ + private: + /* The m_fig1_label is not padded in any way. Stored in EBU Latin Charset */ + std::string m_fig1_label; + /* The flag field selects which label characters make * up the short label */ - uint16_t m_flag = 0xFFFF; + uint16_t m_fig1_flag = 0xFFFF; - /* The m_label is not padded in any way */ - std::string m_label; + /* FIG2 label, stored in UTF-8. TODO: support UCS-2 */ + std::string m_fig2_label; /* Checks and calculates the flag. slabel must be EBU Latin Charset */ - int setShortLabel(const std::string& slabel); + int setFIG1ShortLabel(const std::string& slabel); }; @@ -295,15 +318,7 @@ class DabSubchannel { public: DabSubchannel(std::string& uid) : - uid(uid), - input(), - id(0), - type(subchannel_type_t::DABAudio), - startAddress(0), - bitrate(0), - protection() - { - } + uid(uid) { } // Calculate subchannel size in number of CU unsigned short getSizeCu(void) const; @@ -321,20 +336,17 @@ public: std::string inputUri; std::shared_ptr<Inputs::InputBase> input; - unsigned char id; - subchannel_type_t type; - uint16_t startAddress; - uint16_t bitrate; - dabProtection protection; + unsigned char id = 0; + subchannel_type_t type = subchannel_type_t::DABAudio; + uint16_t startAddress = 0; + uint16_t bitrate = 0; + struct dabProtection protection; }; struct dabAudioComponent { - dabAudioComponent() : - uaType(0xFFFF) {} - - uint16_t uaType; // User Application Type + uint16_t uaType = 0xFFFF; // User Application Type }; @@ -347,16 +359,10 @@ struct dabFidcComponent { struct dabPacketComponent { - dabPacketComponent() : - id(0), - address(0), - appType(0xFFFF), - datagroup(false) { } - - uint16_t id; - uint16_t address; - uint16_t appType; - bool datagroup; + uint16_t id = 0; + uint16_t address = 0; + uint16_t appType = 0xFFFF; + bool datagroup = false; }; class DabComponent : public RemoteControllable @@ -372,10 +378,10 @@ class DabComponent : public RemoteControllable std::string uid; DabLabel label; - uint32_t serviceId; - uint8_t subchId; - uint8_t type; - uint8_t SCIdS; + uint32_t serviceId = 0; + uint8_t subchId = 0; + uint8_t type = 0; + uint8_t SCIdS = 0; dabAudioComponent audio; dabDataComponent data; diff --git a/src/dabOutput/edi/TagItems.cpp b/src/dabOutput/edi/TagItems.cpp index ed8517d..631b88d 100644 --- a/src/dabOutput/edi/TagItems.cpp +++ b/src/dabOutput/edi/TagItems.cpp @@ -132,12 +132,14 @@ std::vector<uint8_t> TagDETI::Assemble() return packet; } -void TagDETI::set_seconds(std::chrono::system_clock::time_point t) +void TagDETI::set_edi_time(const std::chrono::system_clock::time_point& t, int tai_utc_offset) { - std::time_t posix_timestamp_1_jan_2000 = 946684800; - seconds = std::chrono::system_clock::to_time_t(t) - posix_timestamp_1_jan_2000; -} + utco = tai_utc_offset - 32; + + const std::time_t posix_timestamp_1_jan_2000 = 946684800; + seconds = std::chrono::system_clock::to_time_t(t) - posix_timestamp_1_jan_2000 + utco; +} std::vector<uint8_t> TagESTn::Assemble() { diff --git a/src/dabOutput/edi/TagItems.h b/src/dabOutput/edi/TagItems.h index 19284d2..8666053 100644 --- a/src/dabOutput/edi/TagItems.h +++ b/src/dabOutput/edi/TagItems.h @@ -85,15 +85,15 @@ class TagDETI : public TagItem */ uint8_t utco = 0; - void set_tai_utc_offset(int tai_utc_offset) { utco = tai_utc_offset - 32; } + /* Update the EDI time. t is in UTC */ + void set_edi_time(const std::chrono::system_clock::time_point &t, int tai_utc_offset); /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an - * unsigned 32-bit quantity + * unsigned 32-bit quantity. Contrary to POSIX, this value also + * counts leap seconds. */ uint32_t seconds = 0; - void set_seconds(std::chrono::system_clock::time_point t); - /* TSTA: Shall be the 24 least significant bits of the Time Stamp * (TIST) field from the STI-D(LI) Frame. The full definition for the * STI TIST can be found in annex B of EN 300 797 [4]. The most diff --git a/src/fig/FIG0_10.cpp b/src/fig/FIG0_10.cpp index 3ce2847..56ce9fb 100644 --- a/src/fig/FIG0_10.cpp +++ b/src/fig/FIG0_10.cpp @@ -101,22 +101,22 @@ FillStatus FIG0_10::fill(uint8_t *buf, size_t max_size) buf += 2; remaining -= 2; - tm* timeData; + struct tm timeData; time_t dab_time_seconds = 0; uint32_t dab_time_millis = 0; get_dab_time(&dab_time_seconds, &dab_time_millis); - timeData = gmtime(&dab_time_seconds); + gmtime_r(&dab_time_seconds, &timeData); fig0_10->RFU = 0; - fig0_10->setMJD(gregorian2mjd(timeData->tm_year + 1900, - timeData->tm_mon + 1, - timeData->tm_mday)); + fig0_10->setMJD(gregorian2mjd(timeData.tm_year + 1900, + timeData.tm_mon + 1, + timeData.tm_mday)); fig0_10->LSI = 0; fig0_10->ConfInd = 1; fig0_10->UTC = 1; - fig0_10->setHours(timeData->tm_hour); - fig0_10->Minutes = timeData->tm_min; - fig0_10->Seconds = timeData->tm_sec; + fig0_10->setHours(timeData.tm_hour); + fig0_10->Minutes = timeData.tm_min; + fig0_10->Seconds = timeData.tm_sec; fig0_10->setMilliseconds(dab_time_millis); buf += 6; remaining -= 6; diff --git a/src/fig/FIG0_9.cpp b/src/fig/FIG0_9.cpp index cf73625..dcee17c 100644 --- a/src/fig/FIG0_9.cpp +++ b/src/fig/FIG0_9.cpp @@ -139,8 +139,9 @@ FillStatus FIG0_9::fill(uint8_t *buf, size_t max_size) if (ensemble->lto_auto) { time_t now = time(NULL); - struct tm* ltime = localtime(&now); - time_t now2 = timegm(ltime); + struct tm ltime; + localtime_r(&now, <ime); + time_t now2 = timegm(<ime); ensemble->lto = (now2 - now) / 1800; } diff --git a/src/fig/FIG1.cpp b/src/fig/FIG1.cpp index dd1e70b..d0b6a17 100644 --- a/src/fig/FIG1.cpp +++ b/src/fig/FIG1.cpp @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2015 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li Implementation of FIG1 @@ -37,6 +37,12 @@ FillStatus FIG1_0::fill(uint8_t *buf, size_t max_size) auto ensemble = m_rti->ensemble; size_t remaining = max_size; + if (not ensemble->label.has_fig1_label()) { + fs.complete_fig_transmitted = true; + fs.num_bytes_written = 0; + return fs; + } + if (remaining < 22) { fs.num_bytes_written = 0; return fs; @@ -83,14 +89,15 @@ FillStatus FIG1_1::fill(uint8_t *buf, size_t max_size) // Rotate through the subchannels until there is no more // space - for (; service != ensemble->services.end(); - ++service) { - + for (; service != ensemble->services.end(); ++service) { if (remaining < 4 + 16 + 2) { break; } - if ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio || (*service)->getType(ensemble) == subchannel_type_t::DABAudio) { + const auto type = (*service)->getType(ensemble); + + if ( (type == subchannel_type_t::DABPlusAudio or type == subchannel_type_t::DABAudio) and + (*service)->label.has_fig1_label()) { auto fig1_1 = (FIGtype1_1 *)buf; fig1_1->FIGtypeNumber = 1; @@ -148,8 +155,10 @@ FillStatus FIG1_4::fill(uint8_t *buf, size_t max_size) /* We check in the config parser if the primary component has * a label, which is forbidden since V2.1.1 */ + const auto type = (*service)->getType(ensemble); + if (not (*component)->label.long_label().empty() ) { - if ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio || (*service)->getType(ensemble) == subchannel_type_t::DABAudio) { + if (type == subchannel_type_t::DABAudio or type == subchannel_type_t::DABPlusAudio) { if (remaining < 5 + 16 + 2) { break; @@ -173,7 +182,6 @@ FillStatus FIG1_4::fill(uint8_t *buf, size_t max_size) remaining -= 5; } else { // Data - if (remaining < 7 + 16 + 2) { break; } @@ -237,7 +245,10 @@ FillStatus FIG1_5::fill(uint8_t *buf, size_t max_size) break; } - if ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio || (*service)->getType(ensemble) == subchannel_type_t::DABAudio) { + const auto type = (*service)->getType(ensemble); + const bool is_audio = (type == subchannel_type_t::DABAudio or type == subchannel_type_t::DABPlusAudio); + + if (not is_audio) { auto fig1_5 = (FIGtype1_5 *)buf; fig1_5->FIGtypeNumber = 1; fig1_5->Length = 23; diff --git a/src/fig/FIG1.h b/src/fig/FIG1.h index 2cca8d5..0fedffe 100644 --- a/src/fig/FIG1.h +++ b/src/fig/FIG1.h @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2016 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li */ /* diff --git a/src/fig/FIG2.cpp b/src/fig/FIG2.cpp new file mode 100644 index 0000000..afa64eb --- /dev/null +++ b/src/fig/FIG2.cpp @@ -0,0 +1,406 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + Implementation of FIG2 + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMux is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <algorithm> +#include <iomanip> +#include "fig/FIG2.h" +#include "lib/charset/charset.h" + +namespace FIC { + +using namespace std; + +void FIG2_Segments::clear() +{ + segments.clear(); + current_segment_it = segments.end(); +} + +void FIG2_Segments::load(const string& label) +{ + /* ETSI EN 300 401 Clause 5.2.2.3.3: + * "The label may be carried in one or two FIGs" + */ + + if (label.size() > 32) { + throw runtime_error("FIG2 label too long: " + to_string(label.size())); + } + + if (label != label_on_last_load) { + toggle = not toggle; + label_on_last_load = label; + } + + segments.clear(); + vector<uint8_t> label_bytes(label.begin(), label.end()); + + for (size_t i = 0; i < label_bytes.size(); i+=16) { + size_t len = distance(label_bytes.begin() + i, label_bytes.end()); + + len = min(len, (size_t)16); + + segments.emplace_back(label_bytes.begin() + i, label_bytes.begin() + i + len); + } + + current_segment_it = segments.begin(); +} + +size_t FIG2_Segments::segment_count() const { + if (segments.empty()) { + throw runtime_error("Empty FIG2 has not segments"); + } + return segments.size() - 1; +} + +std::vector<uint8_t> FIG2_Segments::advance_segment() { + if (current_segment_it == segments.end()) { + return {}; + } + else { + return *(current_segment_it++); + } +} + +size_t FIG2_Segments::current_segment_length() const { + if (current_segment_it == segments.end()) { + return 0; + } + else { + return current_segment_it->size(); + } +} + +size_t FIG2_Segments::current_segment_index() const { + vv::const_iterator cur = current_segment_it; + return distance(segments.begin(), cur); +} + +bool FIG2_Segments::ready() const { + return not segments.empty(); +} + +bool FIG2_Segments::complete() const { + return not segments.empty() and current_segment_it == segments.end(); +} + +int FIG2_Segments::toggle_flag() const { + return toggle ? 1 : 0; +} + +// Ensemble label +FillStatus FIG2_0::fill(uint8_t *buf, size_t max_size) +{ + FillStatus fs; + auto ensemble = m_rti->ensemble; + ssize_t remaining = max_size; + + if (not ensemble->label.has_fig2_label()) { + fs.complete_fig_transmitted = true; + fs.num_bytes_written = 0; + return fs; + } + + if (not m_segments.ready()) { + m_segments.load(ensemble->label.fig2_label()); + + if (not m_segments.ready()) { + throw logic_error("Non-empty label but segments not ready()"); + } + } + + const ssize_t required_bytes = (m_segments.current_segment_index() == 0) ? + sizeof(FIGtype2) + 2 + sizeof(FIG2_Extended_Label) + m_segments.current_segment_length() : + sizeof(FIGtype2) + 2 + m_segments.current_segment_length(); + + if (remaining < required_bytes) { + fs.num_bytes_written = 0; + return fs; + } + + auto fig2 = (FIGtype2*)buf; + + fig2->Length = required_bytes - 1; + fig2->FIGtypeNumber = 2; + + fig2->Extension = 0; + fig2->Rfu = 0; + fig2->SegmentIndex = m_segments.current_segment_index(); + fig2->ToggleFlag = m_segments.toggle_flag(); + + buf += sizeof(FIGtype2); + remaining -= sizeof(FIGtype2); + + // Identifier field + buf[0] = ensemble->id >> 8; + buf[1] = ensemble->id & 0xFF; + buf += 2; + remaining -= 2; + + if (m_segments.current_segment_index() == 0) { + // EN 300 401 5.2.2.3.3 "The second segment shall be carried in a following + // FIG type 2 data field ...", i.e. do not insert the header anymore. + auto ext = (FIG2_Extended_Label*)buf; + ext->Rfa = 0; + ext->SegmentCount = m_segments.segment_count(); + ext->EncodingFlag = 0; // UTF-8 + ext->CharacterFlag = htons(0xFF00); // Short label always truncation + + buf += sizeof(FIG2_Extended_Label); + remaining -= sizeof(FIG2_Extended_Label); + } + + const auto character_field = m_segments.advance_segment(); + copy(character_field.begin(), character_field.end(), buf); + buf += character_field.size(); + remaining -= character_field.size(); + + if (m_segments.complete()) { + fs.complete_fig_transmitted = true; + m_segments.clear(); + } + fs.num_bytes_written = max_size - remaining; + return fs; +} + +// Programme service label and data service label +FillStatus FIG2_1_and_5::fill(uint8_t *buf, size_t max_size) +{ + FillStatus fs; + + ssize_t remaining = max_size; + + if (not m_initialised) { + service = m_rti->ensemble->services.end(); + m_initialised = true; + } + + auto ensemble = m_rti->ensemble; + + // Rotate through the subchannels until there is no more space + while (service != ensemble->services.end()) { + const bool is_programme = (*service)->getType(ensemble) == subchannel_type_t::DABAudio or + ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio); + + if (not (m_programme xor is_programme) and (*service)->label.has_fig2_label()) { + + auto& segments = segment_per_service[(*service)->id]; + + if (not segments.ready()) { + segments.load((*service)->label.fig2_label()); + + if (not segments.ready()) { + throw logic_error("Non-empty label but segments not ready()"); + } + } + + const size_t id_length = (is_programme ? 2 : 4); + + const ssize_t required_bytes = sizeof(FIGtype2) + id_length + segments.current_segment_length() + + ((segments.current_segment_index() == 0) ? sizeof(FIG2_Extended_Label) : 0); + + if (remaining < required_bytes) { + break; + } + + auto fig2 = (FIGtype2*)buf; + + fig2->Length = required_bytes - 1; + fig2->FIGtypeNumber = 2; + + fig2->Extension = figextension(); + fig2->Rfu = 0; + fig2->SegmentIndex = segments.current_segment_index(); + fig2->ToggleFlag = segments.toggle_flag(); + + buf += sizeof(FIGtype2); + remaining -= sizeof(FIGtype2); + + // Identifier field + if (is_programme) { + buf[0] = (*service)->id >> 8; + buf[1] = (*service)->id & 0xFF; + } + else { + buf[0] = ((*service)->id >> 24) & 0xFF; + buf[1] = ((*service)->id >> 16) & 0xFF; + buf[2] = ((*service)->id >> 8) & 0xFF; + buf[3] = (*service)->id & 0xFF; + } + buf += id_length; + remaining -= id_length; + + if (segments.current_segment_index() == 0) { + auto ext = (FIG2_Extended_Label*)buf; + ext->Rfa = 0; + ext->SegmentCount = segments.segment_count(); + ext->EncodingFlag = 0; // UTF-8 + ext->CharacterFlag = htons(0xFF00); // Short label always truncation + + buf += sizeof(FIG2_Extended_Label); + remaining -= sizeof(FIG2_Extended_Label); + } + + const auto character_field = segments.advance_segment(); + copy(character_field.begin(), character_field.end(), buf); + buf += character_field.size(); + remaining -= character_field.size(); + + if (segments.complete()) { + segments.clear(); + ++service; + } + } + else { + ++service; + } + } + + if (service == ensemble->services.end()) { + service = ensemble->services.begin(); + fs.complete_fig_transmitted = true; + } + + fs.num_bytes_written = max_size - remaining; + return fs; +} + +// Component label +FillStatus FIG2_4::fill(uint8_t *buf, size_t max_size) +{ + FillStatus fs; + + ssize_t remaining = max_size; + + if (not m_initialised) { + component = m_rti->ensemble->components.end(); + m_initialised = true; + } + + auto ensemble = m_rti->ensemble; + + while (component != ensemble->components.end()) { + if ((*component)->label.has_fig2_label()) { + auto service = getService(*component, ensemble->services); + + auto& segments = segment_per_component[{(*component)->serviceId, (*component)->SCIdS}]; + + if (not segments.ready()) { + segments.load((*component)->label.fig2_label()); + + if (not segments.ready()) { + throw logic_error("Non-empty label but segments not ready()"); + } + } + + const bool is_programme = (*service)->getType(ensemble) == subchannel_type_t::DABAudio or + ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio); + + const size_t id_length = is_programme ? + sizeof(FIGtype2_4_Programme_Identifier) : + sizeof(FIGtype2_4_Data_Identifier); + + const ssize_t required_bytes = sizeof(FIGtype2) + id_length + segments.current_segment_length() + + ((segments.current_segment_index() == 0) ? sizeof(FIG2_Extended_Label) : 0); + + if (remaining < required_bytes) { + break; + } + + auto fig2 = (FIGtype2*)buf; + + fig2->Length = required_bytes - 1; + fig2->FIGtypeNumber = 2; + + fig2->Extension = 4; + fig2->Rfu = 0; + fig2->SegmentIndex = segments.current_segment_index(); + fig2->ToggleFlag = segments.toggle_flag(); + + buf += sizeof(FIGtype2); + remaining -= sizeof(FIGtype2); + + // Identifier field + if (is_programme) { + auto fig2_4 = (FIGtype2_4_Programme_Identifier*)buf; + + fig2_4->SCIdS = (*component)->SCIdS; + fig2_4->rfa = 0; + fig2_4->PD = 0; + fig2_4->SId = htons((*service)->id); + + buf += sizeof(FIGtype2_4_Programme_Identifier); + remaining -= sizeof(FIGtype2_4_Programme_Identifier); + } + else { + auto fig2_4 = (FIGtype2_4_Data_Identifier*)buf; + + fig2_4->SCIdS = (*component)->SCIdS; + fig2_4->rfa = 0; + fig2_4->PD = 1; + fig2_4->SId = htonl((*service)->id); + + buf += sizeof(FIGtype2_4_Data_Identifier); + remaining -= sizeof(FIGtype2_4_Data_Identifier); + } + + if (segments.current_segment_index() == 0) { + auto ext = (FIG2_Extended_Label*)buf; + ext->Rfa = 0; + ext->SegmentCount = segments.segment_count(); + ext->EncodingFlag = 0; // UTF-8 + ext->CharacterFlag = htons(0xFF00); // Short label always truncation + + buf += sizeof(FIG2_Extended_Label); + remaining -= sizeof(FIG2_Extended_Label); + } + + const auto character_field = segments.advance_segment(); + copy(character_field.begin(), character_field.end(), buf); + buf += character_field.size(); + remaining -= character_field.size(); + + if (segments.complete()) { + segments.clear(); + ++component; + } + } + else { + ++component; + } + } + + if (component == ensemble->components.end()) { + component = ensemble->components.begin(); + fs.complete_fig_transmitted = true; + } + + fs.num_bytes_written = max_size - remaining; + return fs; +} + +} // namespace FIC + diff --git a/src/fig/FIG2.h b/src/fig/FIG2.h new file mode 100644 index 0000000..6fad658 --- /dev/null +++ b/src/fig/FIG2.h @@ -0,0 +1,163 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMux is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __FIG2_H_ +#define __FIG2_H_ + +#include <cstdint> +#include <map> + +#include "fig/FIG.h" + +namespace FIC { + +class FIG2_Segments { + public: + void clear(); + void load(const std::string& label); + + size_t segment_count() const; + std::vector<uint8_t> advance_segment(); + size_t current_segment_length() const; + size_t current_segment_index() const; + + bool ready() const; + bool complete() const; + int toggle_flag() const; + + private: + using vv = std::vector<std::vector<uint8_t> >; + vv segments; + vv::iterator current_segment_it; + + std::string label_on_last_load; + bool toggle = true; +}; + +// FIG type 2/0, Multiplex Configuration Info (MCI), +// Ensemble information +class FIG2_0 : public IFIG +{ + public: + FIG2_0(FIGRuntimeInformation* rti) : + m_rti(rti) {} + virtual FillStatus fill(uint8_t *buf, size_t max_size); + virtual FIG_rate repetition_rate() const { return FIG_rate::B; } + + virtual int figtype() const { return 2; } + virtual int figextension() const { return 0; } + + private: + FIGRuntimeInformation *m_rti; + FIG2_Segments m_segments; +}; + +// FIG type 2/1, programme service label and FIG type 2/5, data service label +// share this code. +class FIG2_1_and_5 : public IFIG +{ + public: + FIG2_1_and_5(FIGRuntimeInformation* rti, bool programme) : + m_rti(rti), + m_initialised(false), + m_programme(programme) {} + + virtual FillStatus fill(uint8_t *buf, size_t max_size); + virtual FIG_rate repetition_rate() const { return FIG_rate::B; } + + virtual int figtype() const { return 2;} + virtual int figextension() const { return m_programme ? 1 : 5; } + + private: + FIGRuntimeInformation *m_rti; + bool m_initialised; + bool m_programme; + vec_sp_service::iterator service; + std::map<uint32_t, FIG2_Segments> segment_per_service; +}; + +// FIG type 2/4, service component label +class FIG2_4 : public IFIG +{ + public: + FIG2_4(FIGRuntimeInformation* rti) : + m_rti(rti), m_initialised(false) {} + virtual FillStatus fill(uint8_t *buf, size_t max_size); + virtual FIG_rate repetition_rate() const { return FIG_rate::B; } + + virtual int figtype() const { return 2; } + virtual int figextension() const { return 4; } + + private: + FIGRuntimeInformation *m_rti; + bool m_initialised; + vec_sp_component::iterator component; + 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; + + uint8_t Extension:3; + uint8_t Rfu:1; + uint8_t SegmentIndex:3; + uint8_t ToggleFlag:1; +} PACKED; + +struct FIGtype2_4_Programme_Identifier { + uint8_t SCIdS:4; + uint8_t rfa:3; + uint8_t PD:1; + uint16_t SId; +} PACKED; + +struct FIGtype2_4_Data_Identifier { + uint8_t SCIdS:4; + uint8_t rfa:3; + uint8_t PD:1; + uint32_t SId; +} PACKED; + +struct FIG2_Extended_Label { + uint8_t Rfa:4; + uint8_t SegmentCount:3; + uint8_t EncodingFlag:1; + + uint16_t CharacterFlag; +} 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 390dcf3..8ea183c 100644 --- a/src/fig/FIGCarousel.cpp +++ b/src/fig/FIGCarousel.cpp @@ -94,7 +94,11 @@ FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) : m_fig0_18(&m_rti), m_fig0_19(&m_rti), m_fig0_21(&m_rti), - m_fig0_24(&m_rti) + m_fig0_24(&m_rti), + m_fig2_0(&m_rti), + m_fig2_1(&m_rti, true), + m_fig2_5(&m_rti, false), + m_fig2_4(&m_rti) { /* Complete MCI except FIG0/8 should be in FIB0. * EN 300 401 V1.4.1 Clause 6.1 @@ -130,6 +134,11 @@ FIGCarousel::FIGCarousel(std::shared_ptr<dabEnsemble> ensemble) : load_and_allocate(m_fig0_19, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_21, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_24, FIBAllocation::FIB_ANY); + + load_and_allocate(m_fig2_0, FIBAllocation::FIB_ANY); + load_and_allocate(m_fig2_1, FIBAllocation::FIB_ANY); + load_and_allocate(m_fig2_5, FIBAllocation::FIB_ANY); + load_and_allocate(m_fig2_4, FIBAllocation::FIB_ANY); } void FIGCarousel::load_and_allocate(IFIG& fig, FIBAllocation fib) diff --git a/src/fig/FIGCarousel.h b/src/fig/FIGCarousel.h index ac0574d..a07a855 100644 --- a/src/fig/FIGCarousel.h +++ b/src/fig/FIGCarousel.h @@ -32,6 +32,7 @@ #include "fig/FIG.h" #include "fig/FIG0.h" #include "fig/FIG1.h" +#include "fig/FIG2.h" #include <list> #include <map> #include <memory> @@ -110,6 +111,10 @@ class FIGCarousel { FIG0_19 m_fig0_19; FIG0_21 m_fig0_21; FIG0_24 m_fig0_24; + FIG2_0 m_fig2_0; + FIG2_1_and_5 m_fig2_1; + FIG2_1_and_5 m_fig2_5; + FIG2_4 m_fig2_4; }; } // namespace FIC diff --git a/src/utils.cpp b/src/utils.cpp index 212c97f..5f81083 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -211,12 +211,11 @@ void printServices(const vector<shared_ptr<DabService> >& services) for (auto service : services) { etiLog.level(info) << "Service " << service->get_rc_name(); - etiLog.level(info) << " label: " << - service->label.long_label(); - etiLog.level(info) << " short label: " << - service->label.short_label(); - + etiLog.level(info) << " label: " << service->label.long_label(); + etiLog.level(info) << " short label: " << service->label.short_label(); etiLog.log(info, " (0x%x)", service->label.flag()); + etiLog.level(info) << " FIG2 label: " << service->label.fig2_label(); + etiLog.log(info, " id: 0x%lx (%lu)", service->id, service->id); @@ -244,7 +243,7 @@ void printComponents(const vec_sp_component& components) unsigned int index = 0; for (const auto component : components) { - etiLog.level(info) << "Component " << component->get_rc_name(); + etiLog.level(info) << "Component " << component->get_rc_name(); printComponent(component); ++index; } @@ -256,12 +255,11 @@ void printComponent(const shared_ptr<DabComponent>& component) component->serviceId, component->serviceId); etiLog.log(info, " subchannel id: 0x%x (%u)", component->subchId, component->subchId); - etiLog.level(info) << " label: " << - component->label.long_label(); - etiLog.level(info) << " short label: " << - component->label.short_label(); - + etiLog.level(info) << " label: " << component->label.long_label(); + etiLog.level(info) << " short label: " << component->label.short_label(); etiLog.log(info, " (0x%x)", component->label.flag()); + etiLog.level(info) << " FIG2 label: " << component->label.fig2_label(); + etiLog.log(info, " service component type: 0x%x (%u)", component->type, component->type); @@ -516,12 +514,11 @@ void printEnsemble(const shared_ptr<dabEnsemble>& ensemble) etiLog.log(info, "Ensemble"); etiLog.log(info, " id: 0x%lx (%lu)", ensemble->id, ensemble->id); etiLog.log(info, " ecc: 0x%x (%u)", ensemble->ecc, ensemble->ecc); - etiLog.level(info) << " label: " << - ensemble->label.long_label(); - etiLog.level(info) << " short label: " << - ensemble->label.short_label(); - + etiLog.level(info) << " label: " << ensemble->label.long_label(); + etiLog.level(info) << " short label: " << ensemble->label.short_label(); etiLog.log(info, " (0x%x)", ensemble->label.flag()); + etiLog.level(info) << " FIG2 label: " << ensemble->label.fig2_label(); + switch (ensemble->transmission_mode) { case TransmissionMode_e::TM_I: etiLog.log(info, " mode: TM I"); @@ -539,8 +536,9 @@ void printEnsemble(const shared_ptr<dabEnsemble>& ensemble) if (ensemble->lto_auto) { time_t now = time(nullptr); - struct tm* ltime = localtime(&now); - time_t now2 = timegm(ltime); + struct tm ltime; + localtime_r(&now, <ime); + time_t now2 = timegm(<ime); etiLog.log(info, " lto: %2.1f hours", 0.5 * (now2 - now) / 1800); } else { |