summaryrefslogtreecommitdiffstats
path: root/src/fig
diff options
context:
space:
mode:
Diffstat (limited to 'src/fig')
-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
6 files changed, 621 insertions, 12 deletions
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