diff options
Diffstat (limited to 'src/fig')
-rw-r--r-- | src/fig/FIG0_10.cpp | 16 | ||||
-rw-r--r-- | src/fig/FIG0_9.cpp | 5 | ||||
-rw-r--r-- | src/fig/FIG1.cpp | 27 | ||||
-rw-r--r-- | src/fig/FIG1.h | 2 | ||||
-rw-r--r-- | src/fig/FIG2.cpp | 406 | ||||
-rw-r--r-- | src/fig/FIG2.h | 163 | ||||
-rw-r--r-- | src/fig/FIGCarousel.cpp | 11 | ||||
-rw-r--r-- | src/fig/FIGCarousel.h | 5 |
8 files changed, 615 insertions, 20 deletions
diff --git a/src/fig/FIG0_10.cpp b/src/fig/FIG0_10.cpp index 3ce2847..56ce9fb 100644 --- a/src/fig/FIG0_10.cpp +++ b/src/fig/FIG0_10.cpp @@ -101,22 +101,22 @@ FillStatus FIG0_10::fill(uint8_t *buf, size_t max_size) buf += 2; remaining -= 2; - tm* timeData; + struct tm timeData; time_t dab_time_seconds = 0; uint32_t dab_time_millis = 0; get_dab_time(&dab_time_seconds, &dab_time_millis); - timeData = gmtime(&dab_time_seconds); + gmtime_r(&dab_time_seconds, &timeData); fig0_10->RFU = 0; - fig0_10->setMJD(gregorian2mjd(timeData->tm_year + 1900, - timeData->tm_mon + 1, - timeData->tm_mday)); + fig0_10->setMJD(gregorian2mjd(timeData.tm_year + 1900, + timeData.tm_mon + 1, + timeData.tm_mday)); fig0_10->LSI = 0; fig0_10->ConfInd = 1; fig0_10->UTC = 1; - fig0_10->setHours(timeData->tm_hour); - fig0_10->Minutes = timeData->tm_min; - fig0_10->Seconds = timeData->tm_sec; + fig0_10->setHours(timeData.tm_hour); + fig0_10->Minutes = timeData.tm_min; + fig0_10->Seconds = timeData.tm_sec; fig0_10->setMilliseconds(dab_time_millis); buf += 6; remaining -= 6; diff --git a/src/fig/FIG0_9.cpp b/src/fig/FIG0_9.cpp index cf73625..dcee17c 100644 --- a/src/fig/FIG0_9.cpp +++ b/src/fig/FIG0_9.cpp @@ -139,8 +139,9 @@ FillStatus FIG0_9::fill(uint8_t *buf, size_t max_size) if (ensemble->lto_auto) { time_t now = time(NULL); - struct tm* ltime = localtime(&now); - time_t now2 = timegm(ltime); + struct tm ltime; + localtime_r(&now, <ime); + time_t now2 = timegm(<ime); ensemble->lto = (now2 - now) / 1800; } diff --git a/src/fig/FIG1.cpp b/src/fig/FIG1.cpp index dd1e70b..d0b6a17 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,14 +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::DABPlusAudio || (*service)->getType(ensemble) == subchannel_type_t::DABAudio) { + const auto type = (*service)->getType(ensemble); + + if ( (type == subchannel_type_t::DABPlusAudio or type == subchannel_type_t::DABAudio) and + (*service)->label.has_fig1_label()) { auto fig1_1 = (FIGtype1_1 *)buf; fig1_1->FIGtypeNumber = 1; @@ -148,8 +155,10 @@ 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 */ + const auto type = (*service)->getType(ensemble); + if (not (*component)->label.long_label().empty() ) { - if ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio || (*service)->getType(ensemble) == subchannel_type_t::DABAudio) { + if (type == subchannel_type_t::DABAudio or type == subchannel_type_t::DABPlusAudio) { if (remaining < 5 + 16 + 2) { break; @@ -173,7 +182,6 @@ FillStatus FIG1_4::fill(uint8_t *buf, size_t max_size) remaining -= 5; } else { // Data - if (remaining < 7 + 16 + 2) { break; } @@ -237,7 +245,10 @@ FillStatus FIG1_5::fill(uint8_t *buf, size_t max_size) break; } - if ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio || (*service)->getType(ensemble) == subchannel_type_t::DABAudio) { + const auto type = (*service)->getType(ensemble); + const bool is_audio = (type == subchannel_type_t::DABAudio or type == subchannel_type_t::DABPlusAudio); + + if (not is_audio) { 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..afa64eb --- /dev/null +++ b/src/fig/FIG2.cpp @@ -0,0 +1,406 @@ +/* + 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 and data service label +FillStatus FIG2_1_and_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; + + // Rotate through the subchannels until there is no more space + while (service != ensemble->services.end()) { + const bool is_programme = (*service)->getType(ensemble) == subchannel_type_t::DABAudio or + ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio); + + if (not (m_programme xor is_programme) 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 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 = figextension(); + 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) { + buf[0] = (*service)->id >> 8; + buf[1] = (*service)->id & 0xFF; + } + else { + buf[0] = ((*service)->id >> 24) & 0xFF; + buf[1] = ((*service)->id >> 16) & 0xFF; + buf[2] = ((*service)->id >> 8) & 0xFF; + buf[3] = (*service)->id & 0xFF; + } + buf += id_length; + remaining -= id_length; + + 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::DABAudio or + ((*service)->getType(ensemble) == subchannel_type_t::DABPlusAudio); + + const size_t id_length = is_programme ? + sizeof(FIGtype2_4_Programme_Identifier) : + sizeof(FIGtype2_4_Data_Identifier); + + 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; +} + +} // namespace FIC + diff --git a/src/fig/FIG2.h b/src/fig/FIG2.h new file mode 100644 index 0000000..6fad658 --- /dev/null +++ b/src/fig/FIG2.h @@ -0,0 +1,163 @@ +/* + 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 and FIG type 2/5, data service label +// share this code. +class FIG2_1_and_5 : public IFIG +{ + public: + FIG2_1_and_5(FIGRuntimeInformation* rti, bool programme) : + m_rti(rti), + m_initialised(false), + m_programme(programme) {} + + 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 m_programme ? 1 : 5; } + + private: + FIGRuntimeInformation *m_rti; + bool m_initialised; + bool m_programme; + 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; +}; + +#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..8ea183c 100644 --- a/src/fig/FIGCarousel.cpp +++ b/src/fig/FIGCarousel.cpp @@ -94,7 +94,11 @@ 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, true), + m_fig2_5(&m_rti, false), + 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 +134,11 @@ 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_5, 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..a07a855 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,10 @@ class FIGCarousel { FIG0_19 m_fig0_19; FIG0_21 m_fig0_21; FIG0_24 m_fig0_24; + FIG2_0 m_fig2_0; + FIG2_1_and_5 m_fig2_1; + FIG2_1_and_5 m_fig2_5; + FIG2_4 m_fig2_4; }; } // namespace FIC |