/* Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://rd.csp.it/) Copyright (C) 2014, 2015 Matthias P. Braendli (http://opendigitalradio.org) Copyright (C) 2015, 2016, 2017 Stefan Pöschel (http://opendigitalradio.org) 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 . */ /*! \file dls.cpp \brief Dynamic Label (DL) related code \author Sergio Sagliocco \author Matthias P. Braendli \author Stefan Pöschel */ #include "dls.h" // --- DLSManager ----------------------------------------------------------------- const size_t DLSManager::MAXDLS = 128; // chars const size_t DLSManager::DLS_SEG_LEN_PREFIX = 2; const size_t DLSManager::DLS_SEG_LEN_CHAR_MAX = 16; const int DLSManager::DLS_REPETITION_WHILE_SLS = 50; const std::string DLSManager::DL_PARAMS_OPEN = "##### parameters { #####"; const std::string DLSManager::DL_PARAMS_CLOSE = "##### parameters } #####"; DATA_GROUP* DLSManager::createDynamicLabelCommand(uint8_t command) { DATA_GROUP* dg = new DATA_GROUP(2, 2, 3); uint8_vector_t &seg_data = dg->data; // prefix: toggle? + first seg + last seg + command flag + command seg_data[0] = (dls_toggle ? (1 << 7) : 0) + (1 << 6) + (1 << 5) + (1 << 4) + command; // prefix: reserved seg_data[1] = 0; // CRC dg->AppendCRC(); return dg; } DATA_GROUP* DLSManager::createDynamicLabelPlus(const DL_STATE& dl_state) { size_t tags_size = dl_state.dl_plus_tags.size(); size_t len_dl_plus_cmd_field = 1 + 3 * tags_size; DATA_GROUP* dg = new DATA_GROUP(2 + len_dl_plus_cmd_field, 2, 3); uint8_vector_t &seg_data = dg->data; // prefix: toggle? + first seg + last seg + command flag + command seg_data[0] = (dls_toggle ? (1 << 7) : 0) + (1 << 6) + (1 << 5) + (1 << 4) + DLS_CMD_DL_PLUS; // prefix: link bit + length seg_data[1] = (dls_toggle ? (1 << 7) : 0) + (len_dl_plus_cmd_field - 1); // -1 ! // DL Plus tags command: CId + IT + IR + NT seg_data[2] = (DL_PLUS_CMD_TAGS << 4) + (dl_state.dl_plus_item_toggle ? (1 << 3) : 0) + (dl_state.dl_plus_item_running ? (1 << 2) : 0) + (tags_size - 1); // -1 ! for (size_t i = 0; i < tags_size; i++) { // DL Plus tags command: Content Type + Start Marker + Length Marker seg_data[3 + 3 * i] = dl_state.dl_plus_tags[i].content_type & 0x7F; seg_data[4 + 3 * i] = dl_state.dl_plus_tags[i].start_marker & 0x7F; seg_data[5 + 3 * i] = dl_state.dl_plus_tags[i].length_marker & 0x7F; } // CRC dg->AppendCRC(); return dg; } bool DLSManager::parse_dl_param_bool(const std::string &key, const std::string &value, bool &target) { if (value == "0") { target = 0; return true; } if (value == "1") { target = 1; return true; } fprintf(stderr, "ODR-PadEnc Warning: DL parameter '%s' has unsupported value '%s' - ignored\n", key.c_str(), value.c_str()); return false; } bool DLSManager::parse_dl_param_int_dl_plus_tag(const std::string &key, const std::string &value, int &target) { int value_int = atoi(value.c_str()); if (value_int >= 0x00 && value_int <= 0x7F) { target = value_int; return true; } fprintf(stderr, "ODR-PadEnc Warning: DL Plus tag parameter '%s' %d out of range - ignored\n", key.c_str(), value_int); return false; } void DLSManager::parse_dl_params(std::ifstream &dls_fstream, DL_STATE &dl_state) { std::string line; while (std::getline(dls_fstream, line)) { // return on params close if (line == DL_PARAMS_CLOSE) return; // ignore empty lines and comments if (line.empty() || line[0] == '#') continue; // parse key/value pair size_t separator_pos = line.find('='); if (separator_pos == std::string::npos) { fprintf(stderr, "ODR-PadEnc Warning: DL parameter line '%s' without separator - ignored\n", line.c_str()); continue; } std::string key = line.substr(0, separator_pos); std::string value = line.substr(separator_pos + 1); #ifdef DEBUG fprintf(stderr, "parse_dl_params: key: '%s', value: '%s'\n", key.c_str(), value.c_str()); #endif if (key == "DL_PLUS") { parse_dl_param_bool(key, value, dl_state.dl_plus_enabled); continue; } if (key == "DL_PLUS_ITEM_TOGGLE") { parse_dl_param_bool(key, value, dl_state.dl_plus_item_toggle); continue; } if (key == "DL_PLUS_ITEM_RUNNING") { parse_dl_param_bool(key, value, dl_state.dl_plus_item_running); continue; } if (key == "DL_PLUS_TAG") { if (dl_state.dl_plus_tags.size() == 4) { fprintf(stderr, "ODR-PadEnc Warning: DL Plus tag ignored, as already four tags present\n"); continue; } // split value std::vector params = split_string(value, ' '); if (params.size() != 3) { fprintf(stderr, "ODR-PadEnc Warning: DL Plus tag value '%s' does not have three parts - ignored\n", value.c_str()); continue; } int content_type, start_marker, length_marker; if (parse_dl_param_int_dl_plus_tag("content_type", params[0], content_type) & parse_dl_param_int_dl_plus_tag("start_marker", params[1], start_marker) & parse_dl_param_int_dl_plus_tag("length_marker", params[2], length_marker)) dl_state.dl_plus_tags.push_back(DL_PLUS_TAG(content_type, start_marker, length_marker)); continue; } fprintf(stderr, "ODR-PadEnc Warning: DL parameter '%s' unknown - ignored\n", key.c_str()); } fprintf(stderr, "ODR-PadEnc Warning: no param closing tag, so the DLS text will be empty\n"); } void DLSManager::writeDLS(const std::string& dls_file, DABCharset charset, bool raw_dls, bool remove_dls) { std::ifstream dls_fstream(dls_file); if (!dls_fstream.is_open()) { std::cerr << "Could not open " << dls_file << std::endl; return; } DL_STATE dl_state; std::vector dls_lines; std::string line; // Read and convert lines one by one because the converter doesn't understand // line endings while (std::getline(dls_fstream, line)) { if (line.empty()) continue; if (line == DL_PARAMS_OPEN) { parse_dl_params(dls_fstream, dl_state); } else { if (not raw_dls && charset == DABCharset::UTF8) { dls_lines.push_back(charset_converter.convert(line)); } else { dls_lines.push_back(line); } // TODO handle the other charsets accordingly } } std::stringstream ss; for (size_t i = 0; i < dls_lines.size(); i++) { if (i != 0) { if (charset == DABCharset::UCS2_BE) ss << '\0' << '\n'; else ss << '\n'; } // UCS-2 BE: if from file the first byte of \0\n remains, remove it if (charset == DABCharset::UCS2_BE && dls_lines[i].size() % 2) { dls_lines[i].resize(dls_lines[i].size() - 1); } ss << dls_lines[i]; } dl_state.dl_text = ss.str(); if (dl_state.dl_text.size() > MAXDLS) dl_state.dl_text.resize(MAXDLS); // if DL Plus enabled, but no DL Plus tags were added, add the required DUMMY tag if (dl_state.dl_plus_enabled && dl_state.dl_plus_tags.empty()) dl_state.dl_plus_tags.push_back(DL_PLUS_TAG()); if (not raw_dls) charset = DABCharset::COMPLETE_EBU_LATIN; // toggle the toggle bit only on new DL state bool dl_state_is_new = dl_state != dl_state_prev; if (verbose) { fprintf(stderr, "ODR-PadEnc writing %s DLS text \"" "\x1B[36m" "%s" "\x1B[0m" "\"\n", dl_state_is_new ? "new" : "old", dl_state.dl_text.c_str()); if (dl_state.dl_plus_enabled) { fprintf( stderr, "ODR-PadEnc writing %s DL Plus tags (IT/IR: %d/%d): ", dl_state_is_new ? "new" : "old", dl_state.dl_plus_item_toggle ? 1 : 0, dl_state.dl_plus_item_running ? 1 : 0); for (dl_plus_tags_t::const_iterator it = dl_state.dl_plus_tags.begin(); it != dl_state.dl_plus_tags.end(); it++) { if (it != dl_state.dl_plus_tags.begin()) fprintf(stderr, ", "); fprintf(stderr, "%d (S/L: %d/%d)", it->content_type, it->start_marker, it->length_marker); } fprintf(stderr, "\n"); } } DATA_GROUP *remove_label_dg = NULL; if (dl_state_is_new) { if (remove_dls) remove_label_dg = createDynamicLabelCommand(DLS_CMD_REMOVE_LABEL); dls_toggle = !dls_toggle; // indicate changed text dl_state_prev = dl_state; } prepend_dl_dgs(dl_state, charset); if (remove_label_dg) pad_packetizer->AddDG(remove_label_dg, true); } int DLSManager::dls_count(const std::string& text) { size_t text_len = text.size(); return text_len / DLS_SEG_LEN_CHAR_MAX + (text_len % DLS_SEG_LEN_CHAR_MAX ? 1 : 0); } DATA_GROUP* DLSManager::dls_get(const std::string& text, DABCharset charset, int seg_index) { bool first_seg = seg_index == 0; bool last_seg = seg_index == dls_count(text) - 1; int seg_text_offset = seg_index * DLS_SEG_LEN_CHAR_MAX; const char *seg_text_start = text.c_str() + seg_text_offset; size_t seg_text_len = std::min(text.size() - seg_text_offset, DLS_SEG_LEN_CHAR_MAX); DATA_GROUP* dg = new DATA_GROUP(DLS_SEG_LEN_PREFIX + seg_text_len, 2, 3); uint8_vector_t &seg_data = dg->data; // prefix: toggle? + first seg? + last seg? + (seg len - 1) seg_data[0] = (dls_toggle ? (1 << 7) : 0) + (first_seg ? (1 << 6) : 0) + (last_seg ? (1 << 5) : 0) + (seg_text_len - 1); // prefix: charset / seg index seg_data[1] = (first_seg ? (uint8_t) charset : seg_index) << 4; // character field memcpy(&seg_data[DLS_SEG_LEN_PREFIX], seg_text_start, seg_text_len); // CRC dg->AppendCRC(); #ifdef DEBUG fprintf(stderr, "DL segment:"); for (int i = 0; i < seg_data.size(); i++) fprintf(stderr, " %02x", seg_data[i]); fprintf(stderr, "\n"); #endif return dg; } void DLSManager::prepend_dl_dgs(const DL_STATE& dl_state, DABCharset charset) { // process all DL segments int seg_count = dls_count(dl_state.dl_text); std::vector segs; for (int seg_index = 0; seg_index < seg_count; seg_index++) { #ifdef DEBUG fprintf(stderr, "Segment number %d\n", seg_index + 1); #endif segs.push_back(dls_get(dl_state.dl_text, charset, seg_index)); } // if enabled, add DL Plus data group if (dl_state.dl_plus_enabled) segs.push_back(createDynamicLabelPlus(dl_state)); // prepend to packetizer pad_packetizer->AddDGs(segs, true); #ifdef DEBUG fprintf(stderr, "PAD length: %d\n", padlen); fprintf(stderr, "DLS text: %s\n", text.c_str()); fprintf(stderr, "Number of DL segments: %d\n", seg_count); #endif }