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"); | 
