aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--doc/example.mux9
-rw-r--r--lib/charset/charset.cpp4
-rw-r--r--lib/charset/charset.h4
-rw-r--r--src/ConfigParser.cpp10
-rw-r--r--src/MuxElements.cpp61
-rw-r--r--src/MuxElements.h33
-rw-r--r--src/fig/FIG1.cpp25
-rw-r--r--src/fig/FIG1.h2
-rw-r--r--src/fig/FIG2.cpp417
-rw-r--r--src/fig/FIG2.h176
-rw-r--r--src/fig/FIGCarousel.cpp9
-rw-r--r--src/fig/FIGCarousel.h4
-rw-r--r--src/utils.cpp29
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");