diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2019-01-29 15:55:17 +0100 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2019-01-29 15:55:17 +0100 |
commit | 4acf7a34c202293884739b40af699943d7e8e9d9 (patch) | |
tree | 1f6b2d0a8c3646723c7785aa1f3fad25b7aadc1f | |
parent | 2346b17f650e56f70046e95a54f356a8e3e29107 (diff) | |
download | dabmux-4acf7a34c202293884739b40af699943d7e8e9d9.tar.gz dabmux-4acf7a34c202293884739b40af699943d7e8e9d9.tar.bz2 dabmux-4acf7a34c202293884739b40af699943d7e8e9d9.zip |
Add partial FIG2 label support
Add support for Ensemble label, programme services and components,
although the implementation for the component label is not properly
tested. (data services still to be done)
Neither short labels nor UCS-2 are not yet implemented.
Also, support for draftETSI TS 103 176 which redefines the meaning of
Rfa is still pending.
-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"); |