aboutsummaryrefslogtreecommitdiffstats
path: root/src/odr-padenc.cpp
diff options
context:
space:
mode:
authorStefan Pöschel <github@basicmaster.de>2016-09-19 12:56:40 +0200
committerStefan Pöschel <github@basicmaster.de>2016-09-19 13:01:45 +0200
commitc28958f7738880484c0e23fb4fcd1c4c88e364cf (patch)
tree9a95bae1d14a99e7bee98868e4f15542a5cad580 /src/odr-padenc.cpp
parent2216711b268f461736d49a64c1078456db5a8dd8 (diff)
downloadODR-PadEnc-c28958f7738880484c0e23fb4fcd1c4c88e364cf.tar.gz
ODR-PadEnc-c28958f7738880484c0e23fb4fcd1c4c88e364cf.tar.bz2
ODR-PadEnc-c28958f7738880484c0e23fb4fcd1c4c88e364cf.zip
Apply new name
Diffstat (limited to 'src/odr-padenc.cpp')
-rw-r--r--src/odr-padenc.cpp1830
1 files changed, 1830 insertions, 0 deletions
diff --git a/src/odr-padenc.cpp b/src/odr-padenc.cpp
new file mode 100644
index 0000000..06076af
--- /dev/null
+++ b/src/odr-padenc.cpp
@@ -0,0 +1,1830 @@
+/*
+ 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 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 <http://www.gnu.org/licenses/>.
+*/
+/*!
+ \file odr-padenc.cpp
+ \brief Generete PAD data for MOT Slideshow and DLS
+
+ \author Sergio Sagliocco <sergio.sagliocco@csp.it>
+ \author Matthias P. Braendli <matthias@mpb.li>
+ \author Stefan Pöschel <odr@basicmaster.de>
+*/
+
+#include <cstdio>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <cstring>
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <fstream>
+#include <algorithm>
+#include <vector>
+#include <deque>
+#include <list>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <getopt.h>
+#include "config.h"
+#include "charset.h"
+
+
+#if HAVE_MAGICKWAND
+# include <wand/magick_wand.h>
+#endif
+
+#define DEBUG 0
+
+#define SLEEPDELAY_DEFAULT 10 //seconds
+
+extern "C" {
+#include "crc.h"
+}
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+
+#define XSTR(x) #x
+#define STR(x) XSTR(x)
+
+#define MAXSEGLEN 8189 // Bytes (EN 301 234 v2.1.1, ch. 5.1.1)
+#define MAXDLS 128 // chars
+#define MAXSLIDESIZE 51200 // Bytes (TS 101 499 v3.1.1, ch. 9.1.2)
+
+// Roll-over value for fidx
+#define MAXSLIDEID 9999
+
+// How many slides to keep in history
+#define MAXHISTORYLEN 50
+
+// Do not allow the image compressor to go below
+// JPEG quality 40
+#define MINQUALITY 40
+
+// Charsets from TS 101 756
+#define CHARSET_COMPLETE_EBU_LATIN 0 //!< Complete EBU Latin based repertoire
+#define CHARSET_EBU_LATIN_CY_GR 1 //!< EBU Latin based common core, Cyrillic, Greek
+#define CHARSET_EBU_LATIN_AR_HE_CY_GR 2 //!< EBU Latin based core, Arabic, Hebrew, Cyrillic and Greek
+#define CHARSET_ISO_LATIN_ALPHABET_2 3 //!< ISO Latin Alphabet No 2
+#define CHARSET_UCS2_BE 6 //!< ISO/IEC 10646 using UCS-2 transformation format, big endian byte order
+#define CHARSET_UTF8 15 //!< ISO Latin Alphabet No 2
+
+
+typedef std::vector<uint8_t> uint8_vector_t;
+static int verbose = 0;
+
+
+struct MSCDG {
+ // MSC Data Group Header (extension field not supported)
+ unsigned char extflag; // 1 bit
+ unsigned char crcflag; // 1 bit
+ unsigned char segflag; // 1 bit
+ unsigned char accflag; // 1 bit
+ unsigned char dgtype; // 4 bits
+ unsigned char cindex; // 4 bits
+ unsigned char rindex; // 4 bits
+ /// Session header - Segment field
+ unsigned char last; // 1 bit
+ unsigned short int segnum; // 16 bits
+ // Session header - User access field
+ unsigned char rfa; // 3 bits
+ unsigned char tidflag; // 1 bit
+ unsigned char lenid; // 4 bits - Fixed to value 2 in this implemntation
+ unsigned short int tid; // 16 bits
+ // MSC data group data field
+ // Mot Segmentation header
+ unsigned char rcount; // 3 bits
+ unsigned short int seglen; // 13 bits
+ // Mot segment
+ unsigned char* segdata;
+ // MSC data group CRC
+ unsigned short int crc; // 16 bits
+};
+
+/*! Between collection of slides and transmission, the slide data is saved
+ * in this structure.
+ */
+struct slide_metadata_t {
+ // complete path to slide
+ std::string filepath;
+
+ // index, values from 0 to MAXSLIDEID, rolls over
+ int fidx;
+
+ // This is used to define the order in which several discovered
+ // slides are transmitted
+ bool operator<(const slide_metadata_t& other) const {
+ return this->fidx < other.fidx;
+ }
+};
+
+/*! A simple fingerprint for each slide transmitted.
+ * Allows us to reuse the same fidx if the same slide
+ * is transmitted more than once.
+ */
+struct fingerprint_t {
+ // file name
+ std::string s_name;
+ // file size, in bytes
+ off_t s_size;
+ // time of last modification
+ unsigned long s_mtime;
+
+ // assigned fidx, -1 means invalid
+ int fidx;
+
+ /*! The comparison is not done on fidx, only
+ * on the file-specific data
+ */
+ bool operator==(const fingerprint_t& other) const {
+ return (((s_name == other.s_name &&
+ s_size == other.s_size) &&
+ s_mtime == other.s_mtime));
+ }
+
+ void disp(void) {
+ printf("%s_%ld_%lu:%d\n", s_name.c_str(), s_size, s_mtime, fidx);
+ }
+
+ void load_from_file(const char* filepath)
+ {
+ struct stat file_attribue;
+ const char * final_slash;
+
+ stat(filepath, &file_attribue);
+ final_slash = strrchr(filepath, '/');
+
+ // load filename, size and mtime
+ // Save only the basename of the filepath
+ this->s_name.assign((final_slash == NULL) ? filepath : final_slash + 1);
+ this->s_size = file_attribue.st_size;
+ this->s_mtime = file_attribue.st_mtime;
+
+ this->fidx = -1;
+ }
+};
+
+/*! We keep track of transmitted files so that we can retransmit
+ * identical slides with the same index, in case the receivers cache
+ * them.
+ *
+ * \c MAXHISTORYLEN defines for how how many slides we want to keep this
+ * history.
+ */
+class History {
+ public:
+ History(size_t hist_size) :
+ m_hist_size(hist_size),
+ m_last_given_fidx(0) {}
+ void disp_database();
+ // controller of id base on database
+ int get_fidx(const char* filepath);
+
+ private:
+ std::deque<fingerprint_t> m_database;
+
+ size_t m_hist_size;
+
+ int m_last_given_fidx;
+
+ // find the fingerprint fp in database.
+ // returns the fidx when found,
+ // or -1 if not found
+ int find(const fingerprint_t& fp) const;
+
+ // add a new fingerprint into database
+ // returns its fidx
+ void add(fingerprint_t& fp);
+};
+
+
+int encodeFile(int output_fd, const std::string& fname, int fidx, bool raw_slides);
+
+uint8_vector_t createMotHeader(
+ size_t blobsize,
+ int fidx,
+ bool jfif_not_png);
+
+void createMscDG(MSCDG* msc, unsigned short int dgtype, int *cindex, unsigned short int segnum,
+ unsigned short int lastseg, unsigned short int tid, unsigned char* data,
+ unsigned short int datalen);
+
+struct DATA_GROUP;
+DATA_GROUP* packMscDG(MSCDG* msc);
+
+struct DL_STATE;
+void prepend_dl_dgs(const DL_STATE& dl_state, uint8_t charset);
+void writeDLS(int output_fd, const std::string& dls_file, uint8_t charset, bool raw_dls, bool remove_dls);
+
+// PAD related
+#define CRC_LEN 2
+
+struct DATA_GROUP {
+ uint8_vector_t data;
+ int apptype_start;
+ int apptype_cont;
+ size_t written;
+
+ DATA_GROUP(size_t len, int apptype_start, int apptype_cont) {
+ this->data.resize(len);
+ this->apptype_start = apptype_start;
+ this->apptype_cont = apptype_cont;
+ written = 0;
+ }
+
+ void AppendCRC() {
+ uint16_t crc = 0xFFFF;
+ crc = crc16(crc, &data[0], data.size());
+ crc = ~crc;
+#if DEBUG
+ fprintf(stderr, "crc=%04x ~crc=%04x\n", crc, ~crc);
+#endif
+
+ data.push_back((crc & 0xFF00) >> 8);
+ data.push_back((crc & 0x00FF));
+ }
+
+ size_t Available() {
+ return data.size() - written;
+ }
+
+ int Write(uint8_t *write_data, size_t len, int *cont_apptype) {
+ size_t written_now = std::min(len, Available());
+
+ // fill up remaining bytes with zero padding
+ memcpy(write_data, &data[written], written_now);
+ memset(write_data + written_now, 0x00, len - written_now);
+
+ // set app type depending on progress
+ int apptype = written > 0 ? apptype_cont : apptype_start;
+
+ written += written_now;
+
+ // prevent continuation of a different DG having the same type
+ if (cont_apptype)
+ *cont_apptype = Available() > 0 ? apptype_cont : -1;
+
+ return apptype;
+ }
+};
+
+#define SHORT_PAD 6 // F-PAD + 1x CI + 1x 3 bytes data sub-field
+#define VARSIZE_PAD_MIN 8 // F-PAD + 1x CI + end marker + 1x 4 bytes data sub-field
+#define VARSIZE_PAD_MAX 196 // F-PAD + 4x CI + 4x 48 bytes data sub-field
+#define ALLOWED_PADLEN "6 (short X-PAD), 8 to 196 (variable size X-PAD)"
+
+
+
+// MOT Slideshow related
+static int cindex_header = 0;
+static int cindex_body = 0;
+
+
+class MOTHeader {
+private:
+ size_t header_size;
+ uint8_vector_t data;
+
+ void IncrementHeaderSize(size_t size);
+ void AddParamHeader(int pli, int param_id) {data.push_back((pli << 6) | (param_id & 0x3F));}
+public:
+ MOTHeader(size_t body_size, int content_type, int content_subtype);
+
+ void AddExtension(int param_id);
+ void AddExtension8Bit(int param_id, uint8_t data_field);
+ void AddExtension32Bit(int param_id, uint32_t data_field);
+ void AddExtensionVarSize(int param_id, const uint8_t* data_field, size_t data_field_len);
+ const uint8_vector_t GetData() {return data;}
+};
+
+MOTHeader::MOTHeader(size_t body_size, int content_type, int content_subtype)
+: header_size(0), data(uint8_vector_t(7, 0x00)) {
+ // init header core
+
+ // body size
+ data[0] = (body_size >> 20) & 0xFF;
+ data[1] = (body_size >> 12) & 0xFF;
+ data[2] = (body_size >> 4) & 0xFF;
+ data[3] = (body_size << 4) & 0xF0;
+
+ // header size
+ IncrementHeaderSize(data.size());
+
+ // content type
+ data[5] |= (content_type << 1) & 0x7E;
+
+ // content subtype
+ data[5] |= (content_subtype >> 8) & 0x01;
+ data[6] |= content_subtype & 0xFF;
+}
+
+void MOTHeader::IncrementHeaderSize(size_t size) {
+ header_size += size;
+
+ data[3] &= 0xF0;
+ data[3] |= (header_size >> 9) & 0x0F;
+
+ data[4] = (header_size >> 1) & 0xFF;
+
+ data[5] &= 0x7F;
+ data[5] |= (header_size << 7) & 0x80;
+}
+
+void MOTHeader::AddExtension(int param_id) {
+ AddParamHeader(0b00, param_id);
+
+ IncrementHeaderSize(1);
+}
+
+void MOTHeader::AddExtension8Bit(int param_id, uint8_t data_field) {
+ AddParamHeader(0b01, param_id);
+ data.push_back(data_field);
+
+ IncrementHeaderSize(2);
+}
+
+void MOTHeader::AddExtension32Bit(int param_id, uint32_t data_field) {
+ AddParamHeader(0b10, param_id);
+ data.push_back((data_field >> 24) & 0xFF);
+ data.push_back((data_field >> 16) & 0xFF);
+ data.push_back((data_field >> 8) & 0xFF);
+ data.push_back( data_field & 0xFF);
+
+ IncrementHeaderSize(5);
+}
+
+void MOTHeader::AddExtensionVarSize(int param_id, const uint8_t* data_field, size_t data_field_len) {
+ AddParamHeader(0b11, param_id);
+
+ // longer field lens use 15 instead of 7 bits
+ bool ext = data_field_len > 127;
+ if (ext) {
+ data.push_back(0x80 | ((data_field_len >> 8) & 0x7F));
+ data.push_back(data_field_len & 0xFF);
+ } else {
+ data.push_back(data_field_len & 0x7F);
+ }
+
+ for (size_t i = 0; i < data_field_len; i++)
+ data.push_back(data_field[i]);
+
+ IncrementHeaderSize(1 + (ext ? 2 : 1) + data_field_len);
+}
+
+
+
+// DLS related
+#define FPAD_LEN 2
+#define DLS_SEG_LEN_PREFIX 2
+#define DLS_SEG_LEN_CHAR_MAX 16
+#define DLS_CMD_REMOVE_LABEL 0b0001
+#define DLS_CMD_DL_PLUS 0b0010
+#define DL_PLUS_CMD_TAGS 0b0000
+
+#define DL_PARAMS_OPEN "##### parameters { #####"
+#define DL_PARAMS_CLOSE "##### parameters } #####"
+
+CharsetConverter charset_converter;
+
+struct DL_PLUS_TAG {
+ int content_type;
+ int start_marker;
+ int length_marker;
+
+ DL_PLUS_TAG() :
+ content_type(0), // = DUMMY
+ start_marker(0),
+ length_marker(0)
+ {}
+
+ DL_PLUS_TAG(int content_type, int start_marker, int length_marker) :
+ content_type(content_type),
+ start_marker(start_marker),
+ length_marker(length_marker)
+ {}
+
+ bool operator==(const DL_PLUS_TAG& other) const {
+ return
+ content_type == other.content_type &&
+ start_marker == other.start_marker &&
+ length_marker == other.length_marker;
+ }
+ bool operator!=(const DL_PLUS_TAG& other) const {
+ return !(*this == other);
+ }
+};
+
+typedef std::vector<DL_PLUS_TAG> dl_plus_tags_t;
+
+struct DL_STATE {
+ std::string dl_text;
+
+ bool dl_plus_enabled;
+ bool dl_plus_item_toggle;
+ bool dl_plus_item_running;
+ dl_plus_tags_t dl_plus_tags;
+
+ DL_STATE() :
+ dl_plus_enabled(false),
+ dl_plus_item_toggle(false),
+ dl_plus_item_running(false)
+ {}
+
+ bool operator==(const DL_STATE& other) const {
+ if (dl_text != other.dl_text)
+ return false;
+ if (dl_plus_enabled != other.dl_plus_enabled)
+ return false;
+ if (dl_plus_enabled) {
+ if (dl_plus_item_toggle != other.dl_plus_item_toggle)
+ return false;
+ if (dl_plus_item_running != other.dl_plus_item_running)
+ return false;
+ if (dl_plus_tags != other.dl_plus_tags)
+ return false;
+ }
+ return true;
+ }
+ bool operator!=(const DL_STATE& other) const {
+ return !(*this == other);
+ }
+};
+
+
+typedef uint8_vector_t pad_t;
+static bool dls_toggle = false;
+static DL_STATE dl_state_prev;
+
+
+class PADPacketizer {
+private:
+ static const size_t subfield_lens[];
+
+ const size_t xpad_size_max;
+ const bool short_xpad;
+ const size_t max_cis;
+
+ size_t xpad_size;
+ uint8_t subfields[4*48];
+ size_t subfields_size;
+
+ // PAD w/ CI list
+ int ci_type[4];
+ size_t ci_len_index[4];
+ size_t used_cis;
+
+ // PAD w/o CI list
+ int last_ci_type;
+ size_t last_ci_size;
+
+ size_t AddCINeededBytes();
+ void AddCI(int apptype, int len_index);
+
+ int OptimalSubFieldSizeIndex(size_t available_bytes);
+ int WriteDGToSubField(DATA_GROUP* dg, size_t len);
+
+ bool AppendDG(DATA_GROUP* dg);
+ void AppendDGWithCI(DATA_GROUP* dg);
+ void AppendDGWithoutCI(DATA_GROUP* dg);
+
+ void ResetPAD();
+ pad_t* FlushPAD();
+public:
+ std::deque<DATA_GROUP*> queue;
+
+ PADPacketizer(size_t pad_size);
+ ~PADPacketizer();
+
+ pad_t* GetPAD();
+
+ // will be removed, when pull (instead of push) approach is implemented!
+ void WriteAllPADs(int output_fd);
+};
+
+
+const size_t PADPacketizer::subfield_lens[] = {4, 6, 8, 12, 16, 24, 32, 48};
+
+PADPacketizer::PADPacketizer(size_t pad_size) :
+ xpad_size_max(pad_size - FPAD_LEN),
+ short_xpad(pad_size == SHORT_PAD),
+ max_cis(short_xpad ? 1 : 4),
+ last_ci_type(-1)
+{
+ ResetPAD();
+}
+
+PADPacketizer::~PADPacketizer() {
+ while (!queue.empty()) {
+ delete queue.front();
+ queue.pop_front();
+ }
+}
+
+
+pad_t* PADPacketizer::GetPAD() {
+ bool pad_flushable = false;
+
+ // process DG queue
+ while (!pad_flushable && !queue.empty()) {
+ DATA_GROUP* dg = queue.front();
+
+ // repeatedly append DG
+ while (!pad_flushable && dg->Available() > 0)
+ pad_flushable = AppendDG(dg);
+
+ if (dg->Available() == 0) {
+ delete dg;
+ queue.pop_front();
+ }
+ }
+
+ // (possibly empty) PAD
+ return FlushPAD();
+}
+
+void PADPacketizer::WriteAllPADs(int output_fd) {
+ for (;;) {
+ pad_t* pad = GetPAD();
+
+ // if only F-PAD present, abort
+ if (pad->back() == FPAD_LEN) {
+ delete pad;
+ break;
+ }
+
+ if (write(output_fd, &(*pad)[0], pad->size()) != (signed) pad->size())
+ fprintf(stderr, "ODR-PadEnc Error: Could not write PAD\n");
+
+ delete pad;
+ }
+}
+
+
+size_t PADPacketizer::AddCINeededBytes() {
+ // returns the amount of additional bytes needed for the next CI
+
+ // special cases: end marker added/replaced
+ if (!short_xpad && used_cis == 0)
+ return 2;
+ if (!short_xpad && used_cis == (max_cis - 1))
+ return 0;
+ return 1;
+}
+
+void PADPacketizer::AddCI(int apptype, int len_index) {
+ ci_type[used_cis] = apptype;
+ ci_len_index[used_cis] = len_index;
+
+ xpad_size += AddCINeededBytes();
+ used_cis++;
+}
+
+
+int PADPacketizer::OptimalSubFieldSizeIndex(size_t available_bytes) {
+ /*! Return the index of the optimal sub-field size by stepwise search (regards only Variable Size X-PAD):
+ * - find the smallest sub-field able to hold (at least) all available bytes
+ * - find the biggest regarding sub-field we have space for (which definitely exists - otherwise previously the PAD would have been flushed)
+ * - if the wasted space is at least as big as the smallest possible sub-field, use a sub-field one size smaller
+ */
+ int len_index = 0;
+
+ while ((len_index + 1) < 8 && subfield_lens[len_index] < available_bytes)
+ len_index++;
+ while ((len_index - 1) >= 0 && (subfield_lens[len_index] + AddCINeededBytes()) > (xpad_size_max - xpad_size))
+ len_index--;
+ if ((len_index - 1) >= 0 && ((int) subfield_lens[len_index] - (int) available_bytes) >= (int) subfield_lens[0])
+ len_index--;
+
+ return len_index;
+}
+
+int PADPacketizer::WriteDGToSubField(DATA_GROUP* dg, size_t len) {
+ int apptype = dg->Write(&subfields[subfields_size], len, &last_ci_type);
+ subfields_size += len;
+ xpad_size += len;
+ return apptype;
+}
+
+
+bool PADPacketizer::AppendDG(DATA_GROUP* dg) {
+ /*! use X-PAD w/o CIs instead of X-PAD w/ CIs, if we can save some bytes or at least do not waste additional bytes
+ *
+ * Omit CI list in case:
+ * 1. no pending data sub-fields
+ * 2. last CI type valid
+ * 3. last CI type matching current (continuity) CI type
+ * 4a. short X-PAD; OR
+ * 4ba. size of the last X-PAD being at least as big as the available X-PAD payload in case all CIs are used AND
+ * 4bb. the amount of available DG bytes being at least as big as the size of the last X-PAD in case all CIs are used
+ */
+ if (
+ used_cis == 0 &&
+ last_ci_type != -1 &&
+ last_ci_type == dg->apptype_cont &&
+ (short_xpad ||
+ (last_ci_size >= (xpad_size_max - max_cis) &&
+ dg->Available() >= (last_ci_size - max_cis)))
+ ) {
+ AppendDGWithoutCI(dg);
+ return true;
+ } else {
+ AppendDGWithCI(dg);
+
+ // if no further sub-fields could be added, PAD must be flushed
+ if (used_cis == max_cis || subfield_lens[0] + AddCINeededBytes() > (xpad_size_max - xpad_size))
+ return true;
+ }
+ return false;
+}
+
+
+void PADPacketizer::AppendDGWithCI(DATA_GROUP* dg) {
+ int len_index = short_xpad ? 0 : OptimalSubFieldSizeIndex(dg->Available());
+ size_t len_size = short_xpad ? 3 : subfield_lens[len_index];
+
+ int apptype = WriteDGToSubField(dg, len_size);
+ AddCI(apptype, len_index);
+
+#if DEBUG
+ fprintf(stderr, "PADPacketizer: added sub-field w/ CI - type: %2d, size: %2zu\n", apptype, len_size);
+#endif
+}
+
+void PADPacketizer::AppendDGWithoutCI(DATA_GROUP* dg) {
+#if DEBUG
+ int old_last_ci_type = last_ci_type;
+#endif
+
+ WriteDGToSubField(dg, last_ci_size);
+
+#if DEBUG
+ fprintf(stderr, "PADPacketizer: added sub-field w/o CI - type: %2d, size: %2zu\n", old_last_ci_type, last_ci_size);
+#endif
+}
+
+void PADPacketizer::ResetPAD() {
+ xpad_size = 0;
+ subfields_size = 0;
+ used_cis = 0;
+}
+
+pad_t* PADPacketizer::FlushPAD() {
+ pad_t* result = new pad_t(xpad_size_max + FPAD_LEN + 1);
+ pad_t &pad = *result;
+
+ size_t pad_offset = xpad_size_max;
+
+ if (subfields_size > 0) {
+ if (used_cis > 0) {
+ // X-PAD: CIs
+ for (size_t i = 0; i < used_cis; i++)
+ pad[--pad_offset] = (short_xpad ? 0 : ci_len_index[i]) << 5 | ci_type[i];
+
+ // X-PAD: end marker (if needed)
+ if (used_cis < max_cis)
+ pad[--pad_offset] = 0x00;
+ }
+
+ // X-PAD: sub-fields (reversed on-the-fly)
+ for (size_t off = 0; off < subfields_size; off++)
+ pad[--pad_offset] = subfields[off];
+ } else {
+ // no X-PAD
+ last_ci_type = -1;
+ }
+
+ // zero padding
+ memset(&pad[0], 0x00, pad_offset);
+
+ // F-PAD
+ pad[xpad_size_max + 0] = subfields_size > 0 ? (short_xpad ? 0x10 : 0x20) : 0x00;
+ pad[xpad_size_max + 1] = subfields_size > 0 ? (used_cis > 0 ? 0x02 : 0x00) : 0x00;
+
+ // used PAD len
+ pad[xpad_size_max + FPAD_LEN] = xpad_size + FPAD_LEN;
+
+ last_ci_size = xpad_size;
+ ResetPAD();
+ return result;
+}
+
+
+static PADPacketizer *pad_packetizer;
+
+
+
+
+
+
+
+void usage(char* name)
+{
+ fprintf(stderr, "DAB PAD encoder %s for MOT Slideshow and DLS\n\n"
+ "By CSP Innovazione nelle ICT s.c.a r.l. (http://rd.csp.it/) and\n"
+ "Opendigitalradio.org\n\n"
+ "Reads image data from the specified directory, DLS text from a file,\n"
+ "and outputs PAD data to the given FIFO.\n"
+ " http://opendigitalradio.org\n\n",
+#if defined(GITVERSION)
+ GITVERSION
+#else
+ PACKAGE_VERSION
+#endif
+ );
+ fprintf(stderr, "Usage: %s [OPTIONS...]\n", name);
+ fprintf(stderr, " -d, --dir=DIRNAME Directory to read images from.\n"
+ " -e, --erase Erase slides from DIRNAME once they have\n"
+ " been encoded.\n"
+ " -s, --sleep=DELAY Wait DELAY seconds between each slide\n"
+ " Default: " STR(SLEEPDELAY_DEFAULT) "\n"
+ " -o, --output=FILENAME Fifo to write PAD data into.\n"
+ " Default: /tmp/pad.fifo\n"
+ " -t, --dls=FILENAME Fifo or file to read DLS text from.\n"
+ " -p, --pad=LENGTH Set the pad length.\n"
+ " Possible values: " ALLOWED_PADLEN "\n"
+ " Default: 58\n"
+ " -c, --charset=ID ID of the character set encoding used for DLS text input.\n"
+ " ID = 0: Complete EBU Latin based repertoire\n"
+ " ID = 6: ISO/IEC 10646 using UCS-2 BE\n"
+ " ID = 15: ISO/IEC 10646 using UTF-8\n"
+ " Default: 15\n"
+ " -r, --remove-dls Always insert a DLS Remove Label command when replacing a DLS text.\n"
+ " -C, --raw-dls Do not convert DLS texts to Complete EBU Latin based repertoire\n"
+ " character set encoding.\n"
+ " -R, --raw-slides Do not process slides. Integrity checks and resizing\n"
+ " slides is skipped. Use this if you know what you are doing !\n"
+ " It is useful only when -d is used\n"
+ " -v, --verbose Print more information to the console\n"
+ );
+}
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+int main(int argc, char *argv[])
+{
+ int ret;
+ struct dirent *pDirent;
+ int padlen = 58;
+ bool erase_after_tx = false;
+ int sleepdelay = SLEEPDELAY_DEFAULT;
+ bool raw_slides = false;
+ int charset = CHARSET_UTF8;
+ bool raw_dls = false;
+ bool remove_dls = false;
+
+ const char* sls_dir = NULL;
+ const char* output = "/tmp/pad.fifo";
+ std::string dls_file;
+
+ const struct option longopts[] = {
+ {"charset", required_argument, 0, 'c'},
+ {"raw-dls", no_argument, 0, 'C'},
+ {"remove-dls", no_argument, 0, 'r'},
+ {"dir", required_argument, 0, 'd'},
+ {"erase", no_argument, 0, 'e'},
+ {"output", required_argument, 0, 'o'},
+ {"dls", required_argument, 0, 't'},
+ {"pad", required_argument, 0, 'p'},
+ {"sleep", required_argument, 0, 's'},
+ {"raw-slides", no_argument, 0, 'R'},
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {0,0,0,0},
+ };
+
+ int ch=0;
+ int index;
+ while(ch != -1) {
+ ch = getopt_long(argc, argv, "eChRrc:d:o:p:s:t:v", longopts, &index);
+ switch (ch) {
+ case 'c':
+ charset = atoi(optarg);
+ break;
+ case 'C':
+ raw_dls = true;
+ break;
+ case 'r':
+ remove_dls = true;
+ break;
+ case 'd':
+ sls_dir = optarg;
+ break;
+ case 'e':
+ erase_after_tx = true;
+ break;
+ case 'o':
+ output = optarg;
+ break;
+ case 's':
+ sleepdelay = atoi(optarg);
+ break;
+ case 't':
+ dls_file = optarg;
+ break;
+ case 'p':
+ padlen = atoi(optarg);
+ break;
+ case 'R':
+ raw_slides = true;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ return 0;
+ }
+ }
+
+ if (padlen != SHORT_PAD && (padlen < VARSIZE_PAD_MIN || padlen > VARSIZE_PAD_MAX)) {
+ fprintf(stderr, "ODR-PadEnc Error: pad length %d invalid: Possible values: "
+ ALLOWED_PADLEN "\n",
+ padlen);
+ return 2;
+ }
+
+ if (sls_dir && not dls_file.empty()) {
+ fprintf(stderr, "ODR-PadEnc encoding Slideshow from '%s' and DLS from '%s' to '%s'\n",
+ sls_dir, dls_file.c_str(), output);
+ }
+ else if (sls_dir) {
+ fprintf(stderr, "ODR-PadEnc encoding Slideshow from '%s' to '%s'. No DLS.\n",
+ sls_dir, output);
+ }
+ else if (not dls_file.empty()) {
+ fprintf(stderr, "ODR-PadEnc encoding DLS from '%s' to '%s'. No Slideshow.\n",
+ dls_file.c_str(), output);
+ }
+ else {
+ fprintf(stderr, "ODR-PadEnc Error: No DLS nor slideshow to encode !\n");
+ usage(argv[0]);
+ return 1;
+ }
+
+ const char* user_charset;
+ switch (charset) {
+ case CHARSET_COMPLETE_EBU_LATIN:
+ user_charset = "Complete EBU Latin";
+ break;
+ case CHARSET_EBU_LATIN_CY_GR:
+ user_charset = "EBU Latin core, Cyrillic, Greek";
+ break;
+ case CHARSET_EBU_LATIN_AR_HE_CY_GR:
+ user_charset = "EBU Latin core, Arabic, Hebrew, Cyrillic, Greek";
+ break;
+ case CHARSET_ISO_LATIN_ALPHABET_2:
+ user_charset = "ISO Latin Alphabet 2";
+ break;
+ case CHARSET_UCS2_BE:
+ user_charset = "UCS-2 BE";
+ break;
+ case CHARSET_UTF8:
+ user_charset = "UTF-8";
+ break;
+ default:
+ user_charset = "Invalid";
+ charset = -1;
+ break;
+ }
+
+ if (charset == -1) {
+ fprintf(stderr, "ODR-PadEnc Error: Invalid charset!\n");
+ usage(argv[0]);
+ return 1;
+ }
+ else {
+ fprintf(stderr, "ODR-PadEnc using charset %s (%d)\n",
+ user_charset, charset);
+ }
+
+ if (not raw_dls) {
+ switch (charset) {
+ case CHARSET_COMPLETE_EBU_LATIN:
+ // no conversion needed
+ break;
+ case CHARSET_UTF8:
+ fprintf(stderr, "ODR-PadEnc converting DLS texts to Complete EBU Latin\n");
+ break;
+ default:
+ fprintf(stderr, "ODR-PadEnc Error: DLS conversion to EBU is currently only supported for UTF-8 input!\n");
+ return 1;
+ }
+ }
+
+ int output_fd = open(output, O_WRONLY);
+ if (output_fd == -1) {
+ perror("ODR-PadEnc Error: failed to open output");
+ return 3;
+ }
+
+#if HAVE_MAGICKWAND
+ MagickWandGenesis();
+#endif
+
+ pad_packetizer = new PADPacketizer(padlen);
+
+ std::list<slide_metadata_t> slides_to_transmit;
+ History slides_history(MAXHISTORYLEN);
+
+ while(1) {
+ if (sls_dir) { // slide + possibly DLS
+ DIR *pDir = opendir(sls_dir);
+ if (pDir == NULL) {
+ fprintf(stderr, "ODR-PadEnc Error: cannot open directory '%s'\n", sls_dir);
+ return 1;
+ }
+
+ // Add new slides to transmit to list
+ while ((pDirent = readdir(pDir)) != NULL) {
+ if (pDirent->d_name[0] != '.') {
+ char imagepath[256];
+ sprintf(imagepath, "%s/%s", sls_dir, pDirent->d_name);
+
+ slide_metadata_t md;
+ md.filepath = imagepath;
+ md.fidx = slides_history.get_fidx(imagepath);
+ slides_to_transmit.push_back(md);
+
+ if (verbose) {
+ fprintf(stderr, "ODR-PadEnc found slide '%s', fidx %d\n", imagepath, md.fidx);
+ }
+ }
+ }
+
+ closedir(pDir);
+
+#if DEBUG
+ slides_history.disp_database();
+#endif
+
+ // if ATM no slides, transmit at least DLS
+ if (slides_to_transmit.empty()) {
+ if (not dls_file.empty())
+ writeDLS(output_fd, dls_file, charset, raw_dls, remove_dls);
+
+ sleep(sleepdelay);
+ } else {
+ // Sort the list in fidx order
+ slides_to_transmit.sort();
+
+ // Encode the slides
+ for (std::list<slide_metadata_t>::const_iterator it = slides_to_transmit.cbegin();
+ it != slides_to_transmit.cend();
+ ++it) {
+
+ ret = encodeFile(output_fd, it->filepath, it->fidx, raw_slides);
+ if (ret != 1)
+ fprintf(stderr, "ODR-PadEnc Error: cannot encode file '%s'\n", it->filepath.c_str());
+
+ if (erase_after_tx) {
+ if (unlink(it->filepath.c_str()) == -1) {
+ fprintf(stderr, "ODR-PadEnc Error: erasing file '%s' failed: ", it->filepath.c_str());
+ perror("");
+ }
+ }
+
+ // Always retransmit DLS after each slide, we want it to be updated frequently
+ if (not dls_file.empty())
+ writeDLS(output_fd, dls_file, charset, raw_dls, remove_dls);
+
+ sleep(sleepdelay);
+ }
+
+ slides_to_transmit.resize(0);
+ }
+ } else { // only DLS
+ // Always retransmit DLS, we want it to be updated frequently
+ writeDLS(output_fd, dls_file, charset, raw_dls, remove_dls);
+
+ sleep(sleepdelay);
+ }
+ }
+
+ delete pad_packetizer;
+
+ return 1;
+}
+
+
+DATA_GROUP* createDataGroupLengthIndicator(size_t len) {
+ DATA_GROUP* dg = new DATA_GROUP(2, 1, 1); // continuation never used (except for comparison at short X-PAD)
+ uint8_vector_t &data = dg->data;
+
+ // Data Group length
+ data[0] = (len & 0x3F00) >> 8;
+ data[1] = (len & 0x00FF);
+
+ // CRC
+ dg->AppendCRC();
+
+ return dg;
+}
+
+
+void warnOnSmallerImage(size_t height, size_t width, const std::string& fname) {
+ if (height < 240 || width < 320)
+ fprintf(stderr, "ODR-PadEnc Warning: Image '%s' smaller than recommended size (%zu x %zu < 320 x 240 px)\n", fname.c_str(), width, height);
+}
+
+
+/*! Scales the image down if needed,
+ * so that it is 320x240 pixels.
+ * Automatically reduces the quality to make sure the
+ * blobsize is not too large.
+ *
+ * \return the blobsize
+ */
+#if HAVE_MAGICKWAND
+size_t resizeImage(MagickWand* m_wand, unsigned char** blob, const std::string& fname, bool* jfif_not_png)
+{
+ unsigned char* blob_png;
+ unsigned char* blob_jpg;
+ size_t blobsize_png;
+ size_t blobsize_jpg;
+
+ size_t height = MagickGetImageHeight(m_wand);
+ size_t width = MagickGetImageWidth(m_wand);
+
+ while (height > 240 || width > 320) {
+ if (height/240.0 > width/320.0) {
+ width = width * 240.0 / height;
+ height = 240;
+ }
+ else {
+ height = height * 320.0 / width;
+ width = 320;
+ }
+ MagickResizeImage(m_wand, width, height, LanczosFilter, 1);
+ }
+
+ height = MagickGetImageHeight(m_wand);
+ width = MagickGetImageWidth(m_wand);
+
+ // try PNG (zlib level 9 / possibly adaptive filtering)
+ MagickSetImageFormat(m_wand, "png");
+ MagickSetImageCompressionQuality(m_wand, 95);
+ blob_png = MagickGetImageBlob(m_wand, &blobsize_png);
+
+ // try JPG
+ MagickSetImageFormat(m_wand, "jpg");
+
+ blob_jpg = NULL;
+ int quality_jpg = 100;
+ do {
+ free(blob_jpg);
+ quality_jpg -= 5;
+
+ MagickSetImageCompressionQuality(m_wand, quality_jpg);
+ blob_jpg = MagickGetImageBlob(m_wand, &blobsize_jpg);
+ } while (blobsize_jpg > MAXSLIDESIZE && quality_jpg > MINQUALITY);
+
+
+ // check for max size
+ if (blobsize_png > MAXSLIDESIZE && blobsize_jpg > MAXSLIDESIZE) {
+ fprintf(stderr, "ODR-PadEnc: Image Size too large after compression: %zu bytes (PNG), %zu bytes (JPEG)\n",
+ blobsize_png, blobsize_jpg);
+ free(blob_png);
+ free(blob_jpg);
+ return 0;
+ }
+
+ // choose the smaller one (at least one does not exceed the max size)
+ *jfif_not_png = blobsize_jpg < blobsize_png;
+
+ if (verbose) {
+ if (*jfif_not_png)
+ fprintf(stderr, "ODR-PadEnc resized image to %zu x %zu. Size after compression %zu bytes (JPEG, q=%d; PNG was %zu bytes)\n",
+ width, height, blobsize_jpg, quality_jpg, blobsize_png);
+ else
+ fprintf(stderr, "ODR-PadEnc resized image to %zu x %zu. Size after compression %zu bytes (PNG; JPEG was %zu bytes)\n",
+ width, height, blobsize_png, blobsize_jpg);
+ }
+
+ // warn if resized image smaller than default dimension
+ warnOnSmallerImage(height, width, fname);
+
+ free(*jfif_not_png ? blob_png : blob_jpg);
+ *blob = *jfif_not_png ? blob_jpg : blob_png;
+ return *jfif_not_png ? blobsize_jpg : blobsize_png;
+}
+#endif
+
+int encodeFile(int output_fd, const std::string& fname, int fidx, bool raw_slides)
+{
+ int ret = 0;
+ int nseg, lastseglen, i, last, curseglen;
+#if HAVE_MAGICKWAND
+ MagickWand *m_wand = NULL;
+ MagickBooleanType err;
+#endif
+ size_t blobsize, height, width;
+ bool jpeg_progr;
+ unsigned char *blob = NULL;
+ unsigned char *curseg = NULL;
+ MSCDG msc;
+ DATA_GROUP* dgli;
+ DATA_GROUP* mscdg;
+
+ size_t orig_quality;
+ char* orig_format = NULL;
+ /*! We handle JPEG differently, because we want to avoid recompressing the
+ * image if it is suitable as is
+ */
+ bool orig_is_jpeg = false;
+
+ /*! If the original is a PNG, we transmit it as is, if the resolution is correct
+ * and the file is not too large. Otherwise it gets resized and sent as JPEG.
+ */
+ bool orig_is_png = false;
+
+ /*! By default, we do resize the image to 320x240, with a quality such that
+ * the blobsize is at most MAXSLIDESIZE.
+ *
+ * For JPEG input files that are already at the right resolution and at the
+ * right blobsize, we disable this to avoid quality loss due to recompression
+ *
+ * As device support of this feature is optional, we furthermore require JPEG input
+ * files to not have progressive coding.
+ */
+ bool resize_required = true;
+
+ bool jfif_not_png = true;
+
+ if (!raw_slides) {
+#if HAVE_MAGICKWAND
+
+ m_wand = NewMagickWand();
+
+ err = MagickReadImage(m_wand, fname.c_str());
+ if (err == MagickFalse) {
+ fprintf(stderr, "ODR-PadEnc Error: Unable to load image '%s'\n",
+ fname.c_str());
+
+ goto encodefile_out;
+ }
+
+ height = MagickGetImageHeight(m_wand);
+ width = MagickGetImageWidth(m_wand);
+ orig_format = MagickGetImageFormat(m_wand);
+ jpeg_progr = MagickGetImageInterlaceScheme(m_wand) == JPEGInterlace;
+
+ // By default assume that the image has full quality and can be reduced
+ orig_quality = 100;
+
+ // strip unneeded information (profiles, meta data)
+ MagickStripImage(m_wand);
+
+ if (orig_format) {
+ if (strcmp(orig_format, "JPEG") == 0) {
+ orig_quality = MagickGetImageCompressionQuality(m_wand);
+ orig_is_jpeg = true;
+
+ if (verbose) {
+ fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d)."
+ " Original size: %zu x %zu. (%s, q=%zu, progr=%s)\n",
+ fname.c_str(), fidx, width, height, orig_format, orig_quality, jpeg_progr ? "y" : "n");
+ }
+ }
+ else if (strcmp(orig_format, "PNG") == 0) {
+ orig_is_png = true;
+ jfif_not_png = false;
+
+ if (verbose) {
+ fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d)."
+ " Original size: %zu x %zu. (%s)\n",
+ fname.c_str(), fidx, width, height, orig_format);
+ }
+ }
+ else if (verbose) {
+ fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d)."
+ " Original size: %zu x %zu. (%s)\n",
+ fname.c_str(), fidx, width, height, orig_format);
+ }
+
+ free(orig_format);
+ }
+ else {
+ fprintf(stderr, "ODR-PadEnc Warning: Unable to detect image format of '%s'\n",
+ fname.c_str());
+
+ fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d). Original size: %zu x %zu.\n",
+ fname.c_str(), fidx, width, height);
+ }
+
+ if ((orig_is_jpeg || orig_is_png) && height <= 240 && width <= 320 && not jpeg_progr) {
+ // Don't recompress the image and check if the blobsize is suitable
+ blob = MagickGetImageBlob(m_wand, &blobsize);
+
+ if (blobsize <= MAXSLIDESIZE) {
+ if (verbose) {
+ fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d). No resize needed: %zu Bytes\n",
+ fname.c_str(), fidx, blobsize);
+ }
+ resize_required = false;
+ }
+ }
+
+ if (resize_required) {
+ blobsize = resizeImage(m_wand, &blob, fname, &jfif_not_png);
+ }
+ else {
+ // warn if unresized image smaller than default dimension
+ warnOnSmallerImage(height, width, fname);
+ }
+
+#else
+ fprintf(stderr, "ODR-PadEnc has not been compiled with MagickWand, only RAW slides are supported!\n");
+ ret = -1;
+ goto encodefile_out;
+#endif
+ }
+ else { // Use RAW data, it might not even be a jpg !
+ // read file
+ FILE* pFile = fopen(fname.c_str(), "rb");
+ if (pFile == NULL) {
+ fprintf(stderr, "ODR-PadEnc Error: Unable to load file '%s'\n",
+ fname.c_str());
+ goto encodefile_out;
+ }
+
+ // obtain file size:
+ fseek(pFile, 0, SEEK_END);
+ blobsize = ftell(pFile);
+ rewind(pFile);
+
+ if (blobsize > MAXSLIDESIZE) {
+ fprintf(stderr, "ODR-PadEnc Warning: blob in raw-slide '%s' too large\n",
+ fname.c_str());
+ }
+
+ // allocate memory to contain the whole file:
+ blob = (unsigned char*)malloc(sizeof(char) * blobsize);
+ if (blob == NULL) {
+ fprintf(stderr, "ODR-PadEnc Error: Memory allocation error\n");
+ goto encodefile_out;
+ }
+
+ // copy the file into the buffer:
+ if (fread(blob, blobsize, 1, pFile) != 1) {
+ fprintf(stderr, "ODR-PadEnc Error: Could not read file\n");
+ goto encodefile_out;
+ }
+
+ size_t last_dot = fname.rfind(".");
+
+ // default:
+ jfif_not_png = true; // This is how we did it in the past.
+ // It's wrong anyway, so we're at least compatible
+
+ if (last_dot != std::string::npos) {
+ std::string file_extension = fname.substr(last_dot, std::string::npos);
+
+ std::transform(file_extension.begin(), file_extension.end(), file_extension.begin(), ::tolower);
+
+ if (file_extension == ".png") {
+ jfif_not_png = false;
+ }
+ }
+
+ if (pFile != NULL) {
+ fclose(pFile);
+ }
+ }
+
+ if (blobsize) {
+ nseg = blobsize / MAXSEGLEN;
+ lastseglen = blobsize % MAXSEGLEN;
+ if (lastseglen != 0) {
+ nseg++;
+ }
+
+ uint8_vector_t mothdr = createMotHeader(blobsize, fidx, jfif_not_png);
+ // Create the MSC Data Group C-Structure
+ createMscDG(&msc, 3, &cindex_header, 0, 1, fidx, &mothdr[0], mothdr.size());
+ // Generate the MSC DG frame (Figure 9 en 300 401)
+ mscdg = packMscDG(&msc);
+ dgli = createDataGroupLengthIndicator(mscdg->data.size());
+
+ pad_packetizer->queue.push_back(dgli);
+ pad_packetizer->queue.push_back(mscdg);
+
+ for (i = 0; i < nseg; i++) {
+ curseg = blob + i * MAXSEGLEN;
+ if (i == nseg-1) {
+ curseglen = lastseglen;
+ last = 1;
+ }
+ else {
+ curseglen = MAXSEGLEN;
+ last = 0;
+ }
+
+ createMscDG(&msc, 4, &cindex_body, i, last, fidx, curseg, curseglen);
+ mscdg = packMscDG(&msc);
+ dgli = createDataGroupLengthIndicator(mscdg->data.size());
+
+ pad_packetizer->queue.push_back(dgli);
+ pad_packetizer->queue.push_back(mscdg);
+ }
+
+ pad_packetizer->WriteAllPADs(output_fd);
+
+ ret = 1;
+ }
+
+encodefile_out:
+#if HAVE_MAGICKWAND
+ if (m_wand) {
+ m_wand = DestroyMagickWand(m_wand);
+ }
+#endif
+
+ if (blob) {
+ free(blob);
+ }
+ return ret;
+}
+
+
+uint8_vector_t createMotHeader(size_t blobsize, int fidx, bool jfif_not_png)
+{
+ // prepare ContentName
+ uint8_t cntemp[10]; // = 1 + 8 + 1 = charset + name + terminator
+ cntemp[0] = 0x0 << 4; // charset: 0 (Complete EBU Latin based) - doesn't really matter here
+ snprintf((char*) (cntemp + 1), sizeof(cntemp) - 1, "%04d.%s", fidx, jfif_not_png ? "jpg" : "png");
+ if (verbose)
+ fprintf(stderr, "ODR-PadEnc writing image as '%s'\n", cntemp + 1);
+
+ // MOT header - content type: image, content subtype: JFIF / PNG
+ MOTHeader header(blobsize, 0x02, jfif_not_png ? 0x001 : 0x003);
+
+ // TriggerTime: NOW
+ header.AddExtension32Bit(0x05, 0x00000000);
+
+ // ContentName: XXXX.jpg / XXXX.png
+ header.AddExtensionVarSize(0x0C, cntemp, sizeof(cntemp) - 1); // omit terminator
+
+ return header.GetData();
+}
+
+
+void createMscDG(MSCDG* msc, unsigned short int dgtype,
+ int *cindex, unsigned short int segnum, unsigned short int lastseg,
+ unsigned short int tid, unsigned char* data,
+ unsigned short int datalen)
+{
+ msc->extflag = 0;
+ msc->crcflag = 1;
+ msc->segflag = 1;
+ msc->accflag = 1;
+ msc->dgtype = dgtype;
+ msc->cindex = *cindex;
+ msc->rindex = 0;
+ msc->last = lastseg;
+ msc->segnum = segnum;
+ msc->rfa = 0;
+ msc->tidflag = 1;
+ msc->lenid = 2;
+ msc->tid = tid;
+ msc->segdata = data;
+ msc->rcount = 0;
+ msc->seglen = datalen;
+
+ *cindex = (*cindex + 1) % 16; // increment continuity index
+}
+
+
+DATA_GROUP* packMscDG(MSCDG* msc)
+{
+ DATA_GROUP* dg = new DATA_GROUP(9 + msc->seglen, 12, 13);
+ uint8_vector_t &b = dg->data;
+
+ // headers
+ b[0] = (msc->extflag<<7) | (msc->crcflag<<6) | (msc->segflag<<5) |
+ (msc->accflag<<4) | msc->dgtype;
+
+ b[1] = (msc->cindex<<4) | msc->rindex;
+ b[2] = (msc->last<<7) | ((msc->segnum & 0x7F00) >> 8);
+ b[3] = msc->segnum & 0x00FF;
+ b[4] = 0;
+ b[4] = (msc->rfa << 5) | (msc->tidflag << 4) | msc->lenid;
+ b[5] = (msc->tid & 0xFF00) >> 8;
+ b[6] = msc->tid & 0x00FF;
+ b[7] = (msc->rcount << 5) | ((msc->seglen & 0x1F00)>>8);
+ b[8] = msc->seglen & 0x00FF;
+
+ // data field
+ memcpy(&b[9], msc->segdata, msc->seglen);
+
+ // CRC
+ dg->AppendCRC();
+
+ return dg;
+}
+
+
+DATA_GROUP* 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: charset (though irrelevant here)
+ seg_data[1] = CHARSET_COMPLETE_EBU_LATIN;
+
+ // CRC
+ dg->AppendCRC();
+
+ return dg;
+}
+
+DATA_GROUP* 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;
+}
+
+
+std::vector<std::string> split_string(const std::string &s, const char delimiter) {
+ std::vector<std::string> result;
+ std::stringstream ss(s);
+ std::string part;
+
+ while (std::getline(ss, part, delimiter))
+ result.push_back(part);
+ return result;
+}
+
+bool 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 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 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);
+#if 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<std::string> 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 writeDLS(int output_fd, const std::string& dls_file, uint8_t 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<std::string> 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 == CHARSET_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 == CHARSET_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 == CHARSET_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 = CHARSET_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 \"%s\"\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->queue.push_front(remove_label_dg);
+ pad_packetizer->WriteAllPADs(output_fd);
+}
+
+
+int 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* dls_get(const std::string& text, uint8_t 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 = 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 ? charset : seg_index) << 4;
+
+ // character field
+ memcpy(&seg_data[DLS_SEG_LEN_PREFIX], seg_text_start, seg_text_len);
+
+ // CRC
+ dg->AppendCRC();
+
+#if 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 prepend_dl_dgs(const DL_STATE& dl_state, uint8_t charset) {
+ // process all DL segments
+ int seg_count = dls_count(dl_state.dl_text);
+ std::vector<DATA_GROUP*> segs;
+ for (int seg_index = 0; seg_index < seg_count; seg_index++) {
+#if 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->queue.insert(pad_packetizer->queue.begin(), segs.begin(), segs.end());
+
+#if 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
+}
+
+int History::find(const fingerprint_t& fp) const
+{
+ size_t i;
+ for (i = 0; i < m_database.size(); i++) {
+ if (m_database[i] == fp) {
+ // return the id of fingerprint found
+ return m_database[i].fidx;
+ }
+ }
+
+ // return -1 when the database doesn't contain this fingerprint
+ return -1;
+}
+
+void History::add(fingerprint_t& fp)
+{
+ m_database.push_back(fp);
+
+ if (m_database.size() > m_hist_size) {
+ m_database.pop_front();
+ }
+}
+
+void History::disp_database()
+{
+ size_t id;
+ printf("HISTORY DATABASE:\n");
+ if (m_database.empty()) {
+ printf(" empty\n");
+ }
+ else {
+ for (id = 0; id < m_database.size(); id++) {
+ printf(" id %4zu: ", id);
+ m_database[id].disp();
+ }
+ }
+ printf("-----------------\n");
+}
+
+int History::get_fidx(const char* filepath)
+{
+ fingerprint_t fp;
+
+ fp.load_from_file(filepath);
+
+ int idx = find(fp);
+
+ if (idx < 0) {
+ idx = m_last_given_fidx++;
+ fp.fidx = idx;
+
+ if (m_last_given_fidx > MAXSLIDEID) {
+ m_last_given_fidx = 0;
+ }
+
+ add(fp);
+ }
+
+ return idx;
+}
+