diff options
-rw-r--r-- | doc/advanced.mux | 45 | ||||
-rw-r--r-- | doc/example.mux | 5 | ||||
-rw-r--r-- | src/ConfigParser.cpp | 72 | ||||
-rw-r--r-- | src/DabMultiplexer.cpp | 12 | ||||
-rw-r--r-- | src/MuxElements.cpp | 2 | ||||
-rw-r--r-- | src/MuxElements.h | 16 | ||||
-rw-r--r-- | src/fig/FIG0_13.cpp | 119 | ||||
-rw-r--r-- | src/fig/FIG0_13.h | 2 | ||||
-rw-r--r-- | src/fig/FIG0structs.h | 4 | ||||
-rw-r--r-- | src/utils.cpp | 112 | ||||
-rw-r--r-- | src/utils.h | 11 |
11 files changed, 242 insertions, 158 deletions
diff --git a/doc/advanced.mux b/doc/advanced.mux index b77744a..9a8c594 100644 --- a/doc/advanced.mux +++ b/doc/advanced.mux @@ -297,7 +297,21 @@ components { service srv-fu subchannel sub-fu - ; for audio components, the field + ; FIG 0/13 user application was previously configured using the figtype setting. + ; Now more than one user application can be defined per component, using the + ; 'user-applications' entry. Do not use both figtype and user-applications. + + ; If both slideshow (TS 101 499) and SPI (TS 102 818) are carried in PAD, the following + ; needs to be set for FIG 0/13 to be transmitted correctly. + user-applications { + ; Add uaType 0x2 and specify X-PAD App Type = 12 + userapp "slideshow" + + ; Add uaType 0x7 and specify X-PAD App Type = 16 + userapp "spi" + } + + ; Deprecated figtype setting: figtype 0x2 ; defines the User Application Type according to TS 101 756 Table 16: ; 0x2 : MOT Slideshow @@ -310,19 +324,16 @@ components { ; 0x44a : Journaline ; If not defined, the FIG 0/13 is not transmitted for this component - ; for packet components, the fields - ; "user application type in FIG 0/13 for packet mode" - ;figtype - ; and the packet address (mandatory) + ; for packet components, set the packet address (mandatory) ;address - ; are supported, with the same syntax as in the manpage. - ; FIG 0/13 is only transmitted when figtype is defined. - ; The -d option on the command line is: - ;datagroup (true|false) - ; and defaults to false. You should normally set - ;datagroup true - ; if your packet mode subchannel is tranferring an MOT application such - ; as EPG or Slideshow. + ; Whether to use data groups + ;datagroup false + ; (defaults to false) + ; You should normally set 'datagroup true' + ; if your packet mode subchannel is transferring an MOT application such + ; as SPI/EPG or Slideshow. + + ; If you specify the user-application "spi", FIG0/13 will set the user application data to "basic profile" } ; If a service is used in more than one component, the primary component has to @@ -332,13 +343,17 @@ components { comp-lu { service srv-lu subchannel sub-lu - figtype 0x2 + user-applications { + userapp "slideshow" + } } comp-ri { service srv-ri subchannel sub-ri - figtype 0x2 + user-applications { + userapp "slideshow" + } } } diff --git a/doc/example.mux b/doc/example.mux index 862906f..92a644d 100644 --- a/doc/example.mux +++ b/doc/example.mux @@ -260,6 +260,11 @@ components { comp-ri { service srv-ri subchannel sub-ri + + ; If the programme contains slideshow, please also specify the user-application: + user-applications { + userapp "slideshow" + } } } diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 5d73b55..ee7d69f 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -749,9 +749,6 @@ void parse_ptree( throw runtime_error(ss.str()); } - int figType = hexparse(pt_comp.get("figtype", "-1")); - int packet_address = hexparse(pt_comp.get("address", "-1")); - int packet_datagroup = pt_comp.get("datagroup", false); uint8_t component_type = hexparse(pt_comp.get("type", "0")); auto component = make_shared<DabComponent>(componentuid); @@ -807,22 +804,67 @@ void parse_ptree( " components are allowed to have labels."; } - if (figType != -1) { - if (figType >= (1<<12)) { - stringstream ss; - ss << "Component with uid " << componentuid << - ": figtype '" << figType << "' is too large !"; - throw runtime_error(ss.str()); - } + auto pt_ua = pt_comp.get_child_optional("user-applications"); + if (pt_ua) { + for (const auto& ua_entry : *pt_ua) { + const string ua_key = ua_entry.first; + const string ua_value = ua_entry.second.data(); + + if (ua_key != "userapp") { + etiLog.level(error) << "user-applications should only contain 'userapp' keys"; + throw runtime_error("component user-applications definition error"); + } + + userApplication ua; + + // Values from TS 101 756 Table 16 - if (component->isPacketComponent(ensemble->subchannels)) { - component->packet.appType = figType; + if (ua_value == "slideshow") { + ua.uaType = FIG0_13_APPTYPE_SLIDESHOW; + // This was previously hardcoded in FIG0/13 and means "MOT, start of X-PAD data group" + ua.xpadAppType = 12; + } + else if (ua_value == "spi") { + ua.uaType = FIG0_13_APPTYPE_SPI; + ua.xpadAppType = 16; + } + + component->audio.uaTypes.push_back(ua); } - else { - component->audio.uaType = figType; + } + else { + // Setting only figtype is the old format which allows the definition of a single + // user application type only. + int figType = hexparse(pt_comp.get("figtype", "-1")); + if (figType != -1) { + + etiLog.level(warn) << "The figtype setting is deprecated in favour of user-applications. Please see example configurations."; + + if (figType >= (1<<12)) { + stringstream ss; + ss << "Component with uid " << componentuid << + ": figtype '" << figType << "' is too large !"; + throw runtime_error(ss.str()); + } + + userApplication ua; + ua.uaType = figType; + + // This was previously hardcoded in FIG0/13 and means "MOT, start of X-PAD data group" + ua.xpadAppType = 12; + + if (component->isPacketComponent(ensemble->subchannels)) { + component->packet.uaTypes.push_back(ua); + } + else { + component->audio.uaTypes.push_back(ua); + } } + } + if (component->isPacketComponent(ensemble->subchannels)) { + int packet_address = hexparse(pt_comp.get("address", "-1")); if (packet_address != -1) { if (! component->isPacketComponent(ensemble->subchannels)) { stringstream ss; @@ -833,6 +875,8 @@ void parse_ptree( component->packet.address = packet_address; } + + int packet_datagroup = pt_comp.get("datagroup", false); if (packet_datagroup) { if (! component->isPacketComponent(ensemble->subchannels)) { stringstream ss; diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index 5eca126..ec29211 100644 --- a/src/DabMultiplexer.cpp +++ b/src/DabMultiplexer.cpp @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* @@ -743,17 +743,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs void DabMultiplexer::print_info() { // Print settings before starting - etiLog.log(info, "--- Multiplex configuration ---"); printEnsemble(ensemble); - - etiLog.log(info, "--- Subchannels list ---"); - printSubchannels(ensemble->subchannels); - - etiLog.log(info, "--- Services list ---"); - printServices(ensemble->services); - - etiLog.log(info, "--- Components list ---"); - printComponents(ensemble->components); } diff --git a/src/MuxElements.cpp b/src/MuxElements.cpp index cb2d545..1d95cc0 100644 --- a/src/MuxElements.cpp +++ b/src/MuxElements.cpp @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org diff --git a/src/MuxElements.h b/src/MuxElements.h index 77d417b..100e4d7 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org @@ -422,10 +422,20 @@ public: struct dabProtection protection; }; +/* For FIG 0/13 (EN 300 401 Clause 6.3.6) */ +struct userApplication { + /* This 11-bit field identifies the user application that shall be used to decode the data in the channel identified + * by SId and SCIdS. The interpretation of this field shall be as defined in ETSI TS 101 756 [3], table 16. */ + uint16_t uaType = 0xFFFF; + /* X-PAD Application Type: this 5-bit field shall specify the lowest numbered application type used to transport + * this user application (see clause 7.4.3). + * Also See EN 300 401 Table 11 "X-PAD Application types" */ + uint8_t xpadAppType; +}; struct dabAudioComponent { - uint16_t uaType = 0xFFFF; // User Application Type + std::vector<userApplication> uaTypes; }; @@ -437,7 +447,7 @@ struct dabDataComponent { struct dabPacketComponent { uint16_t id = 0; uint16_t address = 0; - uint16_t appType = 0xFFFF; + std::vector<userApplication> uaTypes; bool datagroup = false; }; diff --git a/src/fig/FIG0_13.cpp b/src/fig/FIG0_13.cpp index 314c6e1..582e28e 100644 --- a/src/fig/FIG0_13.cpp +++ b/src/fig/FIG0_13.cpp @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2016 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* @@ -92,9 +92,14 @@ FillStatus FIG0_13::fill(uint8_t *buf, size_t max_size) const auto type = (*subchannel)->type; if ( m_transmit_programme and (type == subchannel_type_t::DABPlusAudio or type == subchannel_type_t::DABAudio) and - (*componentFIG0_13)->audio.uaType != 0xffff) { + (*componentFIG0_13)->audio.uaTypes.size() != 0) { - const int required_size = 3+4+11; + const size_t num_apps = (*componentFIG0_13)->audio.uaTypes.size(); + + const size_t app_length = 2; + static_assert(sizeof(FIG0_13_shortAppInfo) == 3); + static_assert(sizeof(FIG0_13_app) == 4); + const int required_size = sizeof(FIG0_13_shortAppInfo) + num_apps * (sizeof(FIG0_13_app) + app_length); if (fig0 == NULL) { if (remaining < 2 + required_size) { @@ -117,34 +122,42 @@ FillStatus FIG0_13::fill(uint8_t *buf, size_t max_size) FIG0_13_shortAppInfo* info = (FIG0_13_shortAppInfo*)buf; info->SId = htonl((*componentFIG0_13)->serviceId) >> 16; info->SCIdS = (*componentFIG0_13)->SCIdS; - info->No = 1; - buf += 3; - remaining -= 3; - fig0->Length += 3; - - FIG0_13_app* app = (FIG0_13_app*)buf; - app->setType((*componentFIG0_13)->audio.uaType); - app->length = 2; - app->xpad = htons(0x0c3c); - /* xpad meaning - CA = 0 - CAOrg = 0 - Rfu = 0 - AppTy(5) = 12 (MOT, start of X-PAD data group) - DG = 0 (MSC data groups used) - Rfu = 0 - DSCTy(6) = 60 (MOT) - */ - - buf += 2 + app->length; - remaining -= 2 + app->length; - fig0->Length += 2 + app->length; + info->No = num_apps; + buf += sizeof(FIG0_13_shortAppInfo); + remaining -= sizeof(FIG0_13_shortAppInfo); + fig0->Length += sizeof(FIG0_13_shortAppInfo); + + for (const auto& ua : (*componentFIG0_13)->audio.uaTypes) { + FIG0_13_app* app = (FIG0_13_app*)buf; + app->setType(ua.uaType); + app->length = app_length; + + const uint8_t dscty = 60; // TS 101 756 Table 2b (MOT) + app->xpad = htons((ua.xpadAppType << 8) | dscty); + /* xpad meaning + CA = 0 + CAOrg = 0 (CAOrg field absent) + Rfu = 0 + AppTy(5) = depending on config + DG = 0 (MSC data groups used) + Rfu = 0 + DSCTy(6) = 60 (MOT) + */ + + buf += sizeof(FIG0_13_app); + remaining -= sizeof(FIG0_13_app); + fig0->Length += sizeof(FIG0_13_app); + } } - else if (!m_transmit_programme && - (*subchannel)->type == subchannel_type_t::Packet && - (*componentFIG0_13)->packet.appType != 0xffff) { + else if (not m_transmit_programme and + (*subchannel)->type == subchannel_type_t::Packet and + (*componentFIG0_13)->packet.uaTypes.size() != 0) { - const int required_size = 5+2+2; + const size_t num_apps = (*componentFIG0_13)->audio.uaTypes.size(); + + const size_t app_length = 2; + const int required_size = sizeof(FIG0_13_longAppInfo) + num_apps * (sizeof(FIG0_13_app) + app_length); + /* is conservative because app_length can be 0 */ if (fig0 == NULL) { if (remaining < 2 + required_size) { @@ -167,26 +180,36 @@ FillStatus FIG0_13::fill(uint8_t *buf, size_t max_size) FIG0_13_longAppInfo* info = (FIG0_13_longAppInfo*)buf; info->SId = htonl((*componentFIG0_13)->serviceId); info->SCIdS = (*componentFIG0_13)->SCIdS; - info->No = 1; - buf += 5; - remaining -= 5; - fig0->Length += 5; - - FIG0_13_app* app = (FIG0_13_app*)buf; - app->setType((*componentFIG0_13)->packet.appType); - if (app->typeLow == FIG0_13_APPTYPE_EPG) { - app->length = 2; - app->xpad = htons(0x0100); - /* xpad used to hold two bytes of EPG profile information - 01 = basic profile - 00 = list terminator */ - } - else { - app->length = 0; + info->No = num_apps; + buf += sizeof(FIG0_13_longAppInfo); + remaining -= sizeof(FIG0_13_longAppInfo); + fig0->Length += sizeof(FIG0_13_longAppInfo); + + for (const auto& ua : (*componentFIG0_13)->audio.uaTypes) { + FIG0_13_app* app = (FIG0_13_app*)buf; + app->setType(ua.uaType); + + size_t effective_length = sizeof(FIG0_13_app); + + if (ua.uaType == FIG0_13_APPTYPE_SPI) { + // TODO This should probably be user configurable... + app->length = app_length; + app->xpad = htons(0x0100); + /* xpad is actually not the "X-PAD data" as in Figure 25, but is the actual user application data. + * We just recycle the same structure, even though it's a bit ugly. + * It holds two bytes of EPG profile information: + * 01 = basic profile + * 00 = list terminator */ + } + else { + app->length = 0; + effective_length = 1; // FIG0_13_app without xpad + } + + buf += effective_length; + remaining -= effective_length; + fig0->Length += effective_length; } - buf += 2 + app->length; - remaining -= 2 + app->length; - fig0->Length += 2 + app->length; } } diff --git a/src/fig/FIG0_13.h b/src/fig/FIG0_13.h index bac5b25..18159f0 100644 --- a/src/fig/FIG0_13.h +++ b/src/fig/FIG0_13.h @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2016 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* diff --git a/src/fig/FIG0structs.h b/src/fig/FIG0structs.h index d022dc4..a82e59e 100644 --- a/src/fig/FIG0structs.h +++ b/src/fig/FIG0structs.h @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* @@ -36,7 +36,7 @@ #define FIG0_13_APPTYPE_TPEG 0x4 #define FIG0_13_APPTYPE_DGPS 0x5 #define FIG0_13_APPTYPE_TMC 0x6 -#define FIG0_13_APPTYPE_EPG 0x7 +#define FIG0_13_APPTYPE_SPI 0x7 #define FIG0_13_APPTYPE_DABJAVA 0x8 #define FIG0_13_APPTYPE_JOURNALINE 0x441 diff --git a/src/utils.cpp b/src/utils.cpp index 7cd441a..c2916d2 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org @@ -38,6 +38,8 @@ using namespace std; static time_t dab_time_seconds = 0; static int dab_time_millis = 0; +static void printServices(const vector<shared_ptr<DabService> >& services); + void update_dab_time() { if (dab_time_seconds == 0) { @@ -100,7 +102,7 @@ void header_message() fprintf(stderr, "(Communications Research Centre Canada)\n\n"); fprintf(stderr, - "Copyright (C) 2019 Matthias P. Braendli\n"); + "Copyright (C) 2020 Matthias P. Braendli\n"); fprintf(stderr, "LICENCE: GPLv3+\n\n"); fprintf(stderr, @@ -209,6 +211,7 @@ void printServices(const vector<shared_ptr<DabService> >& services) { int index = 0; + etiLog.log(info, "--- Services list ---"); for (auto service : services) { etiLog.level(info) << "Service " << service->get_rc_name(); @@ -239,18 +242,7 @@ void printServices(const vector<shared_ptr<DabService> >& services) } } -void printComponents(const vec_sp_component& components) -{ - unsigned int index = 0; - - for (const auto component : components) { - etiLog.level(info) << "Component " << component->get_rc_name(); - printComponent(component); - ++index; - } -} - -void printComponent(const shared_ptr<DabComponent>& component) +void printComponent(const shared_ptr<DabComponent>& component, const std::shared_ptr<dabEnsemble>& ensemble) { etiLog.log(info, " service id: 0x%x (%u)", component->serviceId, component->serviceId); @@ -264,55 +256,55 @@ void printComponent(const shared_ptr<DabComponent>& component) etiLog.log(info, " service component type: 0x%x (%u)", component->type, component->type); - if (component->packet.appType != 0xFFFF) { + if (component->isPacketComponent(ensemble->subchannels)) { etiLog.log(info, " (packet) id: 0x%x (%u)", component->packet.id, component->packet.id); etiLog.log(info, " (packet) address: %u", component->packet.address); - - etiLog.log(info, " (packet) app type: %u", - component->packet.appType); - etiLog.log(info, " (packet) datagroup: %u", component->packet.datagroup); - } - else if (component->audio.uaType != 0xFFFF) { - stringstream ss; - ss << " (audio) app type: "; - switch (component->audio.uaType) { - case FIG0_13_APPTYPE_SLIDESHOW: - ss << "MOT Slideshow"; - break; - case FIG0_13_APPTYPE_WEBSITE: - ss << "MOT Broadcast Website"; - break; - case FIG0_13_APPTYPE_TPEG: - ss << "TPEG"; - break; - case FIG0_13_APPTYPE_DGPS: - ss << "DGPS"; - break; - case FIG0_13_APPTYPE_TMC: - ss << "TMC"; - break; - case FIG0_13_APPTYPE_EPG: - ss << "EPG"; - break; - case FIG0_13_APPTYPE_DABJAVA: - ss << "DAB Java"; - break; - case FIG0_13_APPTYPE_JOURNALINE: - ss << "Journaline"; - break; - default: - ss << "Unknown: " << component->audio.uaType; - break; - } - etiLog.level(info) << ss.str(); + for (const auto& userapp : component->packet.uaTypes) { + etiLog.log(info, " (packet) app type: %u", + userapp.uaType); + } } else { - etiLog.level(info) << " No app type defined"; + for (const auto& userapp : component->audio.uaTypes) { + stringstream ss; + ss << " (audio) app type: "; + switch (userapp.uaType) { + case FIG0_13_APPTYPE_SLIDESHOW: + ss << "MOT Slideshow"; + break; + case FIG0_13_APPTYPE_WEBSITE: + ss << "MOT Broadcast Website"; + break; + case FIG0_13_APPTYPE_TPEG: + ss << "TPEG"; + break; + case FIG0_13_APPTYPE_DGPS: + ss << "DGPS"; + break; + case FIG0_13_APPTYPE_TMC: + ss << "TMC"; + break; + case FIG0_13_APPTYPE_SPI: + ss << "SPI/EPG"; + break; + case FIG0_13_APPTYPE_DABJAVA: + ss << "DAB Java"; + break; + case FIG0_13_APPTYPE_JOURNALINE: + ss << "Journaline"; + break; + default: + ss << "Unknown: " << userapp.uaType; + break; + } + + etiLog.level(info) << ss.str(); + } } } @@ -322,6 +314,8 @@ void printSubchannels(const vec_sp_subchannel& subchannels) int total_num_cu = 0; + etiLog.log(info, "--- Subchannels list ---"); + for (auto subchannel : subchannels) { dabProtection* protection = &subchannel->protection; etiLog.level(info) << "Subchannel " << subchannel->uid; @@ -509,6 +503,7 @@ static void printFrequencyInformation(const shared_ptr<dabEnsemble>& ensemble) void printEnsemble(const shared_ptr<dabEnsemble>& ensemble) { + etiLog.log(info, "--- Multiplex configuration ---"); 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); @@ -556,6 +551,15 @@ void printEnsemble(const shared_ptr<dabEnsemble>& ensemble) printLinking(ensemble); printFrequencyInformation(ensemble); + + printSubchannels(ensemble->subchannels); + printServices(ensemble->services); + + etiLog.log(info, "--- Components list ---"); + for (const auto component : ensemble->components) { + etiLog.level(info) << "Component " << component->get_rc_name(); + printComponent(component, ensemble); + } } long hexparse(const std::string& input) diff --git a/src/utils.h b/src/utils.h index c9beb8e..cf64c69 100644 --- a/src/utils.h +++ b/src/utils.h @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li This file contains a set of utility functions that are used to show @@ -68,17 +68,10 @@ void printUsageConfigfile(char *name, FILE* out = stderr); * resp. subchannels*/ void printOutputs(const std::vector<std::shared_ptr<DabOutput> >& outputs); -void printServices(const std::vector<std::shared_ptr<DabService> >& services); - -void printComponents(const vec_sp_component& components); - -void printSubchannels(const vec_sp_subchannel& subchannels); - /* Print information about the whole ensemble */ void printEnsemble(const std::shared_ptr<dabEnsemble>& ensemble); -/* Print detailed component information */ -void printComponent(const std::shared_ptr<DabComponent>& component); +void printSubchannels(const vec_sp_subchannel& subchannels); long hexparse(const std::string& input); |