aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--src/ensembledatabase.cpp60
-rw-r--r--src/ensembledatabase.hpp39
-rw-r--r--src/etianalyse.cpp42
-rw-r--r--src/fig1.cpp8
-rw-r--r--src/fig2.cpp291
-rw-r--r--src/figs.cpp4
-rw-r--r--src/figs.hpp30
-rw-r--r--src/utils.cpp12
-rw-r--r--src/utils.hpp2
10 files changed, 438 insertions, 51 deletions
diff --git a/Makefile.am b/Makefile.am
index 6c1b712..311f123 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -41,6 +41,7 @@ etisnoop_SOURCES = src/dabplussnoop.cpp src/dabplussnoop.hpp \
src/fig0_8.cpp \
src/fig0_9.cpp \
src/fig1.cpp \
+ src/fig2.cpp \
src/figs.cpp src/figs.hpp \
src/firecode.c src/firecode.h \
src/lib_crc.c src/lib_crc.h \
diff --git a/src/ensembledatabase.cpp b/src/ensembledatabase.cpp
index 671eb40..1353c24 100644
--- a/src/ensembledatabase.cpp
+++ b/src/ensembledatabase.cpp
@@ -1,6 +1,6 @@
/*
Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
- Copyright (C) 2017 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2019 Matthias P. Braendli (http://www.opendigitalradio.org)
Copyright (C) 2015 Data Path
This program is free software: you can redistribute it and/or modify
@@ -27,12 +27,70 @@
*/
+#include <locale>
+#include <codecvt>
+#include <sstream>
#include "ensembledatabase.hpp"
namespace ensemble_database {
using namespace std;
+static string ucs2toutf8(const uint8_t *ucs2, size_t len_bytes)
+{
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> ucsconv;
+
+ wstring ucs2label;
+
+ for (size_t i = 0; i < len_bytes-1; i+=2) {
+ ucs2label += (wchar_t)(ucs2[i] * 256 + ucs2[i+1]);
+ }
+
+ // Can throw range_error
+ return ucsconv.to_bytes(ucs2label);
+}
+
+
+string label_t::assemble() const
+{
+ vector<uint8_t> segments_cat;
+ for (size_t i = 0; i < segment_count; i++) {
+ if (segments.count(i) == 0) {
+ return "";
+ }
+ else {
+ const auto& s = segments.at(i);
+ copy(s.begin(), s.end(), back_inserter(segments_cat));
+ }
+ }
+
+ switch (charset) {
+ case extended_label_charset::UTF8:
+ return string(segments_cat.begin(), segments_cat.end());
+ case extended_label_charset::UCS2:
+ try {
+ return ucs2toutf8(segments_cat.data(), segments_cat.size());
+ }
+ catch (const range_error&) {
+ return "";
+ }
+ }
+ throw logic_error("invalid charset");
+}
+
+string label_t::assembly_state() const
+{
+ stringstream ss;
+ ss << "[";
+ for (const auto& s : segments) {
+ ss << s.first << ",";
+ }
+
+ ss << "count=" << segment_count << "]";
+
+ return ss.str();
+}
+
component_t& service_t::get_component(uint32_t subchannel_id)
{
for (auto& component : components) {
diff --git a/src/ensembledatabase.hpp b/src/ensembledatabase.hpp
index b0c9320..cc080ec 100644
--- a/src/ensembledatabase.hpp
+++ b/src/ensembledatabase.hpp
@@ -1,6 +1,6 @@
/*
Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
- Copyright (C) 2017 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2019 Matthias P. Braendli (http://www.opendigitalradio.org)
Copyright (C) 2015 Data Path
This program is free software: you can redistribute it and/or modify
@@ -36,9 +36,35 @@
#include <stdexcept>
#include <string>
#include <list>
+#include <vector>
+#include <map>
namespace ensemble_database {
+enum class extended_label_charset {
+ UTF8, // encoding flag = 0
+ UCS2, // encoding flag = 1
+};
+
+struct label_t {
+ // FIG 1 Label and shortlabel
+ std::string label;
+ uint16_t shortlabel_flag;
+
+ // Extended Label from FIG 2
+ std::map<int, std::vector<uint8_t> > segments;
+ size_t segment_count = 0; // number if actual segments (not segment count as in spec)
+ extended_label_charset charset;
+ uint8_t toggle_flag = 0;
+
+ // Assemble all segments into a UTF-8 string. Returns an
+ // empty string if not all segments received.
+ std::string assemble() const;
+
+ // Return a string that represents segment count and completeness
+ std::string assembly_state() const;
+};
+
struct subchannel_t {
uint8_t id;
uint8_t start_addr;
@@ -66,8 +92,8 @@ struct component_t {
bool primary;
- std::string label;
- uint16_t shortlabel_flag;
+ label_t label;
+
/* TODO
uint8_t type;
@@ -77,8 +103,8 @@ struct component_t {
struct service_t {
uint32_t id;
- std::string label;
- uint16_t shortlabel_flag;
+ label_t label;
+
bool programme_not_data;
std::list<component_t> components;
@@ -98,8 +124,7 @@ class not_found : public std::runtime_error
struct ensemble_t {
uint16_t EId;
- std::string label;
- uint16_t shortlabel_flag;
+ label_t label;
std::list<service_t> services;
std::list<subchannel_t> subchannels;
diff --git a/src/etianalyse.cpp b/src/etianalyse.cpp
index 8619a59..b419593 100644
--- a/src/etianalyse.cpp
+++ b/src/etianalyse.cpp
@@ -91,6 +91,19 @@ static void print_fig_result(const fig_result_t& fig_result, const display_setti
}
}
+static std::string flag_to_shortlabel(const ensemble_database::label_t label)
+{
+ stringstream shortlabel;
+ for (size_t i = 0; i < label.label.size(); ++i) {
+ if (label.shortlabel_flag & 0x8000 >> i) {
+ shortlabel << label.label[i];
+ }
+ }
+
+ return shortlabel.str();
+}
+
+
void ETI_Analyser::analyse()
{
if (config.etifd != nullptr) {
@@ -565,9 +578,8 @@ void ETI_Analyser::eti_analyse()
fprintf(stat_fd, "---\n");
fprintf(stat_fd, "ensemble:\n");
fprintf(stat_fd, " id: 0x%x\n", ensemble.EId);
- fprintf(stat_fd, " label: %s\n", ensemble.label.c_str());
- fprintf(stat_fd, " shortlabel: %s\n",
- flag_to_shortlabel(ensemble.label, ensemble.shortlabel_flag).c_str());
+ fprintf(stat_fd, " label: %s\n", ensemble.label.label.c_str());
+ fprintf(stat_fd, " shortlabel: %s\n", flag_to_shortlabel(ensemble.label).c_str());
fprintf(stat_fd, "audio:\n");
for (const auto& snoop : config.streams_to_decode) {
@@ -580,12 +592,12 @@ void ETI_Analyser::eti_analyse()
corresponding_service_found = true;
fprintf(stat_fd, " - service_id: 0x%x\n", service.id);
fprintf(stat_fd, " subchannel_id: 0x%x\n", component.subchId);
- fprintf(stat_fd, " label: %s\n", service.label.c_str());
- fprintf(stat_fd, " shortlabel: %s\n",
- flag_to_shortlabel(service.label, service.shortlabel_flag).c_str());
- if (not component.label.empty()) {
- fprintf(stat_fd, " component_label: %s\n", component.label.c_str());
+ fprintf(stat_fd, " label: %s\n", service.label.label.c_str());
+ fprintf(stat_fd, " shortlabel: %s\n", flag_to_shortlabel(service.label).c_str());
+ if (not component.label.label.empty()) {
+ fprintf(stat_fd, " component_label: %s\n", component.label.label.c_str());
}
+ // TODO FIG2 labels
try {
const auto& subch = ensemble.get_subchannel(component.subchId);
@@ -808,13 +820,11 @@ void ETI_Analyser::decodeFIG(
break;
case 2:
{// EXTENDED LABELS
- const uint16_t ext = f[0] & 0x07;
- const display_settings_t disp(config.is_fig_to_be_printed(figtype, ext), indent);
-
fig2_common_t fig2(ensemble, f, figlen);
+ const display_settings_t disp(config.is_fig_to_be_printed(figtype, fig2.ext()), indent);
auto fig_result = fig2_select(fig2, disp);
- printvalue("FIG", disp, "", strprintf("2/%d", ext));
+ printvalue("FIG", disp, "", strprintf("2/%d", fig2.ext()));
if (get_verbosity() > 0) {
printbuf("Data", disp, f, figlen);
@@ -822,15 +832,13 @@ void ETI_Analyser::decodeFIG(
if (disp.print) {
printvalue("Length", disp, "", to_string(figlen));
- printvalue("RFU", disp, "", to_string(fig2.rfu()));
- printvalue("Toggle flag", disp, "", to_string(fig2.toggle_flag()));
- printvalue("Segment index", disp, "", to_string(fig2.segment_index()));
}
figs.push_back(figtype, fig2.ext(), figlen);
- bool complete = true;
- rate_announce_fig(figtype, ext, complete);
+ printvalue("Decoding", disp);
+ print_fig_result(fig_result, disp+1);
+ rate_announce_fig(figtype, fig2.ext(), fig_result.complete);
}
break;
case 5:
diff --git a/src/fig1.cpp b/src/fig1.cpp
index 0a77d83..665544c 100644
--- a/src/fig1.cpp
+++ b/src/fig1.cpp
@@ -61,8 +61,8 @@ fig_result_t fig1_select(fig1_common_t& fig1, const display_settings_t &disp)
if (fig1.fibcrccorrect) {
fig1.ensemble.EId = eid;
- fig1.ensemble.label = label;
- fig1.ensemble.shortlabel_flag = flag;
+ fig1.ensemble.label.label = label;
+ fig1.ensemble.label.shortlabel_flag = flag;
}
}
break;
@@ -78,8 +78,8 @@ fig_result_t fig1_select(fig1_common_t& fig1, const display_settings_t &disp)
if (fig1.fibcrccorrect) {
try {
auto& service = fig1.ensemble.get_service(sid);
- service.label = label;
- service.shortlabel_flag = flag;
+ service.label.label = label;
+ service.label.shortlabel_flag = flag;
}
catch (ensemble_database::not_found &e) {
r.errors.push_back("Not yet in DB");
diff --git a/src/fig2.cpp b/src/fig2.cpp
new file mode 100644
index 0000000..5c1227a
--- /dev/null
+++ b/src/fig2.cpp
@@ -0,0 +1,291 @@
+/*
+ Copyright (C) 2019 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <string>
+#include <cstring>
+
+using namespace std;
+
+static const size_t header_length = 1; // FIG data field header
+
+static void handle_ext_label_data_field(fig2_common_t& fig2, ensemble_database::label_t& label, const display_settings_t &disp, fig_result_t& r)
+{
+ const uint8_t *f = fig2.f + header_length + fig2.identifier_len();
+ const size_t len_bytes = fig2.figlen - header_length - fig2.identifier_len();
+
+ if (label.toggle_flag != fig2.toggle_flag()) {
+ label.segments.clear();
+ label.toggle_flag = fig2.toggle_flag();
+ }
+
+ size_t len_character_field = len_bytes;
+
+ if (fig2.segment_index() == 0) {
+ // Only if it's the first segment
+ const uint8_t encoding_flag = (f[0] & 0x80) >> 7;
+ const uint8_t segment_count = (f[0] & 0x70) >> 4;
+ label.segment_count = segment_count + 1;
+
+ r.msgs.push_back(strprintf("encoding=%s", (encoding_flag ? "UCS-2" : "UTF-8")));
+ r.msgs.push_back(strprintf("Total number of segments=%d", segment_count + 1));
+
+ if (encoding_flag) {
+ label.charset = ensemble_database::extended_label_charset::UCS2;
+ }
+ else {
+ label.charset = ensemble_database::extended_label_charset::UTF8;
+ }
+
+ if (fig2.rfu() == 0) {
+ const uint8_t rfa = (f[0] & 0x0F);
+ r.msgs.push_back(strprintf("rfa=%d", rfa));
+ const uint16_t char_flag = f[1] * 256 + f[2];
+ r.msgs.push_back(strprintf("character flag=%d", char_flag));
+
+ if (len_bytes <= 3) {
+ throw runtime_error("FIG2 label length too short");
+ }
+
+ f += 3;
+ len_character_field -= 3;
+ }
+ else {
+ // ETSI TS 103 176 draft V2.2.1 (2018-08) gives a new meaning to rfu
+ const uint8_t text_control = (f[0] & 0x0F);
+ r.msgs.push_back(strprintf("character flag=0x%02x", text_control));
+
+ if (len_bytes <= 1) {
+ throw runtime_error("FIG2 label length too short");
+ }
+
+ f += 1;
+ len_character_field -= 1;
+ }
+ }
+
+ vector<uint8_t> labelbytes(f, f + len_character_field);
+ label.segments[fig2.segment_index()] = labelbytes;
+}
+
+// UTF-8 or UCS2 Labels
+fig_result_t fig2_select(fig2_common_t& fig2, const display_settings_t &disp)
+{
+ fig_result_t r;
+ const uint8_t *f = fig2.f;
+
+ // FIG data field
+ r.msgs.push_back(strprintf("toggle flag=%d", fig2.toggle_flag()));
+ r.msgs.push_back(strprintf("segment index=%d", fig2.segment_index()));
+ r.msgs.push_back(strprintf("rfu=%d", fig2.rfu()));
+
+ // ext is followed by Identifier field of Type 2 field,
+ // whose length depends on ext
+
+ if (not fig2.fibcrccorrect) {
+ // TODO
+ }
+
+ switch (fig2.ext()) {
+ case 0: // Ensemble label
+ { // ETSI EN 300 401 8.1.13
+ uint16_t eid = f[1] * 256 + f[2];
+ if (fig2.figlen <= header_length + fig2.identifier_len()) {
+ r.errors.push_back("FIG2 length error");
+ }
+ else {
+ r.msgs.push_back(strprintf("Ensemble ID=0x%04X", eid));
+ handle_ext_label_data_field(fig2, fig2.ensemble.label, disp, r);
+
+ const auto complete_label = fig2.ensemble.label.assemble();
+ r.msgs.push_back(strprintf("Label segments=\"%s\"", fig2.ensemble.label.assembly_state().c_str()));
+ if (not complete_label.empty()) {
+ r.msgs.push_back(strprintf("Label=\"%s\"", complete_label.c_str()));
+ }
+ }
+ }
+ break;
+
+ case 1: // Programme service label
+ { // ETSI EN 300 401 8.1.14.1
+ uint16_t sid = f[1] * 256 + f[2];
+ if (fig2.figlen <= header_length + fig2.identifier_len()) {
+ r.errors.push_back("FIG2 length error");
+ }
+ else {
+ r.msgs.push_back(strprintf("Service ID=0x%04X", sid));
+ try {
+ auto& service = fig2.ensemble.get_service(sid);
+ handle_ext_label_data_field(fig2, service.label, disp, r);
+
+ const auto complete_label = service.label.assemble();
+ r.msgs.push_back(strprintf("Label segments=\"%s\"", service.label.assembly_state().c_str()));
+ if (not complete_label.empty()) {
+ r.msgs.push_back(strprintf("Label=\"%s\"", complete_label.c_str()));
+ }
+ }
+ catch (ensemble_database::not_found &e) {
+ r.errors.push_back("Not yet in DB");
+ }
+ }
+ }
+ break;
+
+ case 4: // Service component label
+ { // ETSI EN 300 401 8.1.14.3
+ uint32_t sid;
+ uint8_t pd = (f[1] & 0x80) >> 7;
+ uint8_t SCIdS = f[1] & 0x0F;
+ if (pd == 0) {
+ sid = f[2] * 256 + \
+ f[3];
+ }
+ else {
+ sid = f[2] * 256 * 256 * 256 + \
+ f[3] * 256 * 256 + \
+ f[4] * 256 + \
+ f[5];
+ }
+ if (fig2.figlen <= header_length + fig2.identifier_len()) {
+ r.errors.push_back("FIG2 length error");
+ }
+ else {
+ if (pd == 0) {
+ r.msgs.push_back(strprintf("Service ID=0x%04X", sid));
+ }
+ else {
+ r.msgs.push_back(strprintf("Service ID=0x%08X", sid));
+ }
+ r.msgs.push_back(strprintf("Service Component ID=0x%04X", SCIdS));
+
+ try {
+ auto& service = fig2.ensemble.get_service(sid);
+ handle_ext_label_data_field(fig2, service.label, disp, r);
+
+ const auto complete_label = service.label.assemble();
+ r.msgs.push_back(strprintf("Label segments=\"%s\"", service.label.assembly_state().c_str()));
+ if (not complete_label.empty()) {
+ r.msgs.push_back(strprintf("Label=\"%s\"", complete_label.c_str()));
+ }
+ }
+ catch (ensemble_database::not_found &e) {
+ r.errors.push_back("Not yet in DB");
+ }
+ }
+ }
+ break;
+
+ case 5: // Data service label
+ { // ETSI EN 300 401 8.1.14.2
+ uint32_t sid = f[1] * 256 * 256 * 256 + \
+ f[2] * 256 * 256 + \
+ f[3] * 256 + \
+ f[4];
+
+ if (fig2.figlen <= header_length + fig2.identifier_len()) {
+ r.errors.push_back("FIG2 length error");
+ }
+ else {
+ r.msgs.push_back(strprintf("Service ID=0x%04X", sid));
+
+ try {
+ auto& service = fig2.ensemble.get_service(sid);
+ handle_ext_label_data_field(fig2, service.label, disp, r);
+
+ const auto complete_label = service.label.assemble();
+ r.msgs.push_back(strprintf("Label segments=\"%s\"", service.label.assembly_state().c_str()));
+ if (not complete_label.empty()) {
+ r.msgs.push_back(strprintf("Label=\"%s\"", complete_label.c_str()));
+ }
+ }
+ catch (ensemble_database::not_found &e) {
+ r.errors.push_back("Not yet in DB");
+ }
+ }
+ }
+ break;
+
+
+ case 6: // X-PAD user application label
+ { // ETSI EN 300 401 8.1.14.4
+ uint32_t sid;
+ uint8_t pd, SCIdS, xpadapp;
+ string xpadappdesc;
+
+ pd = (f[1] & 0x80) >> 7;
+ SCIdS = f[1] & 0x0F;
+ if (pd == 0) {
+ sid = f[2] * 256 + \
+ f[3];
+ xpadapp = f[4] & 0x1F;
+ }
+ else {
+ sid = f[2] * 256 * 256 * 256 + \
+ f[3] * 256 * 256 + \
+ f[4] * 256 + \
+ f[5];
+ xpadapp = f[6] & 0x1F;
+ }
+
+ if (fig2.figlen <= header_length + fig2.identifier_len()) {
+ r.errors.push_back("FIG2 length error");
+ }
+ else {
+ if (xpadapp == 2) {
+ xpadappdesc = "DLS";
+ }
+ else if (xpadapp == 12) {
+ xpadappdesc = "MOT";
+ }
+ else {
+ xpadappdesc = "?";
+ }
+
+ ensemble_database::label_t label;
+ // TODO handle multi-segment labels
+
+ handle_ext_label_data_field(fig2, label, disp, r);
+
+ const auto complete_label = label.assemble();
+ r.msgs.push_back(strprintf("Label segments=\"%s\"", label.assembly_state().c_str()));
+ if (not complete_label.empty()) {
+ r.msgs.push_back(strprintf("Label=\"%s\"", complete_label.c_str()));
+ }
+
+ r.msgs.push_back(strprintf("Service ID=0x%04X", sid));
+ r.msgs.push_back(strprintf("Service Component ID=0x%04X", SCIdS));
+ r.msgs.push_back(strprintf("X-PAD App=%02X (", xpadapp) + xpadappdesc + ")");
+ }
+ }
+ break;
+ }
+
+ // FIG2s always contain a complete set of information
+ r.complete = true;
+ return r;
+}
+
diff --git a/src/figs.cpp b/src/figs.cpp
index 6f8a119..62b103e 100644
--- a/src/figs.cpp
+++ b/src/figs.cpp
@@ -111,7 +111,3 @@ fig_result_t fig0_select(fig0_common_t& fig0, const display_settings_t &disp)
}
-fig_result_t fig2_select(fig2_common_t& fig2, const display_settings_t &disp)
-{
- return {};
-}
diff --git a/src/figs.hpp b/src/figs.hpp
index bd144d7..7169fc7 100644
--- a/src/figs.hpp
+++ b/src/figs.hpp
@@ -119,10 +119,32 @@ struct fig2_common_t {
uint8_t* f;
uint16_t figlen;
- uint8_t toggle_flag() { return (f[0] & 0x80) >> 7; }
- uint8_t segment_index() { return (f[0] & 0x70) >> 4; }
- uint16_t rfu() { return (f[0] & 0x08) >> 3; }
- uint16_t ext() { return f[0] & 0x07; }
+ uint8_t toggle_flag() const { return (f[0] & 0x80) >> 7; }
+ uint8_t segment_index() const { return (f[0] & 0x70) >> 4; }
+ uint16_t rfu() const { return (f[0] & 0x08) >> 3; }
+ uint16_t ext() const { return f[0] & 0x07; }
+ size_t identifier_len() const {
+ switch (ext()) {
+ case 0: // Ensemble label
+ return 2;
+ case 1: // Programme service label
+ return 2;
+ case 4: // Service component label
+ {
+ uint8_t pd = (f[1] & 0x80) >> 7;
+ return (pd == 0) ? 3 : 5;
+ }
+ case 5: // Data service label
+ return 5;
+ case 6: // X-PAD user application label
+ {
+ uint8_t pd = (f[1] & 0x80) >> 7;
+ return (pd == 0) ? 4 : 6;
+ }
+ default:
+ return 0;
+ }
+ }
};
// FIG 0/11 and 0/22 struct
diff --git a/src/utils.cpp b/src/utils.cpp
index 2d5af6a..f69b013 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -305,15 +305,3 @@ int absolute_to_dB(int16_t value)
return value ? round(20*log10((double)value / int16_max)) : -90;
}
-std::string flag_to_shortlabel(const std::string& label, uint16_t flag)
-{
- stringstream shortlabel;
- for (size_t i = 0; i < label.size(); ++i) {
- if (flag & 0x8000 >> i) {
- shortlabel << label[i];
- }
- }
-
- return shortlabel.str();
-}
-
diff --git a/src/utils.hpp b/src/utils.hpp
index e473166..524ef09 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -103,5 +103,3 @@ std::string pnum_to_str(uint16_t Programme_Number);
// -90dB
int absolute_to_dB(int16_t value);
-// Calculate the shortlabel from the label and the flag
-std::string flag_to_shortlabel(const std::string& label, uint16_t flag);