diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | doc/example.mux | 9 | ||||
-rw-r--r-- | lib/charset/charset.cpp | 4 | ||||
-rw-r--r-- | lib/charset/charset.h | 4 | ||||
-rw-r--r-- | src/ConfigParser.cpp | 10 | ||||
-rw-r--r-- | src/MuxElements.cpp | 61 | ||||
-rw-r--r-- | src/MuxElements.h | 33 | ||||
-rw-r--r-- | src/fig/FIG1.cpp | 25 | ||||
-rw-r--r-- | src/fig/FIG1.h | 2 | ||||
-rw-r--r-- | src/fig/FIG2.cpp | 417 | ||||
-rw-r--r-- | src/fig/FIG2.h | 176 | ||||
-rw-r--r-- | src/fig/FIGCarousel.cpp | 9 | ||||
-rw-r--r-- | src/fig/FIGCarousel.h | 4 | ||||
-rw-r--r-- | src/utils.cpp | 29 |
14 files changed, 720 insertions, 65 deletions
diff --git a/Makefile.am b/Makefile.am index 64aa02b..fb6bae3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,6 +160,8 @@ odr_dabmux_SOURCES =src/DabMux.cpp \ src/fig/FIG0_24.h \ src/fig/FIG1.cpp \ src/fig/FIG1.h \ + src/fig/FIG2.cpp \ + src/fig/FIG2.h \ src/fig/FIGCarousel.cpp \ src/fig/FIGCarousel.h \ src/fig/TransitionHandler.h \ diff --git a/doc/example.mux b/doc/example.mux index 48a89dc..caa4d2d 100644 --- a/doc/example.mux +++ b/doc/example.mux @@ -107,6 +107,8 @@ ensemble { ; automatic calculation of the local time offset, set the environment variable TZ ; to your timezone (e.g. TZ=Europe/Rome) before you launch ODR-DabMux + ; FIG1 labels are given with the 'label' and 'shortlabel' keys. + ; ; All labels are maximum 16 characters in length. ; Labels that are valid utf-8 will be converted to EBU Latin Character set ; as defined in ETSI TS 101 756, in Annex C. If it's not valid utf-8, the @@ -118,6 +120,11 @@ ensemble { ; be longer than 8 characters. If omitted, it will be truncated from the ; label shortlabel "ODR" + + ; The FIG2 label can be up to 16 characters long, and is in UTF-8. + ;fig2_label "ÓpêñÐigıtålRadiō" + + ; There is no FIG2 short label for the moment. } ; Definition of DAB services @@ -127,7 +134,7 @@ services { srv-fu { id 0x4daa label "Fünk" - ; You can define a shortlabel too. + ; You can define a shortlabel and a fig2_label too. } srv-ri { ; If your ensemble contains a service from another country, diff --git a/lib/charset/charset.cpp b/lib/charset/charset.cpp index 1abc097..f5eb216 100644 --- a/lib/charset/charset.cpp +++ b/lib/charset/charset.cpp @@ -73,7 +73,7 @@ CharsetConverter::CharsetConverter() } } -std::string CharsetConverter::convert(std::string line_utf8, bool up_to_first_error) +std::string CharsetConverter::utf8_to_ebu(std::string line_utf8, bool up_to_first_error) { string::iterator end_it; @@ -107,7 +107,7 @@ std::string CharsetConverter::convert(std::string line_utf8, bool up_to_first_er return encoded_line; } -std::string CharsetConverter::convert_ebu_to_utf8(const std::string& str) +std::string CharsetConverter::ebu_to_utf8(const std::string& str) { string utf8_str; for (const uint8_t c : str) { diff --git a/lib/charset/charset.h b/lib/charset/charset.h index 8476ee7..5f5899e 100644 --- a/lib/charset/charset.h +++ b/lib/charset/charset.h @@ -39,12 +39,12 @@ class CharsetConverter * stream. If up_to_first_error is set, convert as much text as possible. * If false, raise an utf8::exception in case of conversion errors. */ - std::string convert(std::string line_utf8, bool up_to_first_error = true); + std::string utf8_to_ebu(std::string line_utf8, bool up_to_first_error = true); /*! Convert a EBU Latin byte stream to a UTF-8 encoded string. * Invalid input characters are converted to ⁇ (unicode U+2047). */ - std::string convert_ebu_to_utf8(const std::string& str); + std::string ebu_to_utf8(const std::string& str); private: // Representation of the table in 32-bit unicode diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index f51eb03..07aee0e 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/MuxElements.cpp b/src/MuxElements.cpp index 9c1fc7a..5bdbcdc 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 d116a4e..635f55f 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -166,27 +166,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); }; diff --git a/src/fig/FIG1.cpp b/src/fig/FIG1.cpp index 8f41239..7171a87 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,15 +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::Audio) { - auto fig1_1 = (FIGtype1_1 *)buf; + if ((*service)->getType(ensemble) == subchannel_type_t::Audio and + (*service)->label.has_fig1_label()) { + + auto fig1_1 = (FIGtype1_1*)buf; fig1_1->FIGtypeNumber = 1; fig1_1->Length = 21; @@ -148,9 +154,8 @@ 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 */ - if (not (*component)->label.long_label().empty() ) { + if ((*component)->label.has_fig1_label() ) { if ((*service)->getType(ensemble) == subchannel_type_t::Audio) { - if (remaining < 5 + 16 + 2) { break; } @@ -173,7 +178,6 @@ FillStatus FIG1_4::fill(uint8_t *buf, size_t max_size) remaining -= 5; } else { // Data - if (remaining < 7 + 16 + 2) { break; } @@ -237,7 +241,8 @@ FillStatus FIG1_5::fill(uint8_t *buf, size_t max_size) break; } - if ((*service)->getType(ensemble) != subchannel_type_t::Audio) { + if ((*service)->getType(ensemble) != subchannel_type_t::Audio and + (*service)->label.has_fig1_label()) { 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..a5cbe04 --- /dev/null +++ b/src/fig/FIG2.cpp @@ -0,0 +1,417 @@ +/* + 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 +FillStatus FIG2_1::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()) { + if ((*service)->getType(ensemble) == subchannel_type_t::Audio 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 ssize_t required_bytes = (segments.current_segment_index() == 0) ? + sizeof(FIGtype2) + 2 + sizeof(FIG2_Extended_Label) + segments.current_segment_length() : + sizeof(FIGtype2) + 2 + segments.current_segment_length(); + + if (remaining < required_bytes) { + break; + } + + auto fig2 = (FIGtype2*)buf; + + fig2->Length = required_bytes - 1; + fig2->FIGtypeNumber = 2; + + fig2->Extension = 1; + fig2->Rfu = 0; + fig2->SegmentIndex = segments.current_segment_index(); + fig2->ToggleFlag = segments.toggle_flag(); + + buf += sizeof(FIGtype2); + remaining -= sizeof(FIGtype2); + + // Identifier field + buf[0] = (*service)->id >> 8; + buf[1] = (*service)->id & 0xFF; + buf += 2; + remaining -= 2; + + 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::Audio; + + 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 = 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; +} + +// Data service label +FillStatus FIG2_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; + + service = ensemble->services.end(); // TODO + + if (service == ensemble->services.end()) { + service = ensemble->services.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..b742c89 --- /dev/null +++ b/src/fig/FIG2.h @@ -0,0 +1,176 @@ +/* + 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 +class FIG2_1 : public IFIG +{ + public: + FIG2_1(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 1; } + + private: + FIGRuntimeInformation *m_rti; + bool m_initialised; + 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; +}; + +// FIG type 2/5, data service label +class FIG2_5 : public IFIG +{ + public: + FIG2_5(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 5; } + + private: + FIGRuntimeInformation *m_rti; + bool m_initialised; + vec_sp_service::iterator service; +}; + +#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..c0cebf7 100644 --- a/src/fig/FIGCarousel.cpp +++ b/src/fig/FIGCarousel.cpp @@ -94,7 +94,10 @@ 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), + 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 +133,10 @@ 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_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..9797554 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,9 @@ class FIGCarousel { FIG0_19 m_fig0_19; FIG0_21 m_fig0_21; FIG0_24 m_fig0_24; + FIG2_0 m_fig2_0; + FIG2_1 m_fig2_1; + FIG2_4 m_fig2_4; }; } // namespace FIC diff --git a/src/utils.cpp b/src/utils.cpp index 7a922fe..02595b7 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); @@ -513,12 +511,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"); |