diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/odr-padenc.cpp | 854 | ||||
-rw-r--r-- | src/pad_common.cpp | 14 | ||||
-rw-r--r-- | src/pad_common.h | 2 | ||||
-rw-r--r-- | src/sls.cpp | 692 | ||||
-rw-r--r-- | src/sls.h | 230 |
6 files changed, 946 insertions, 848 deletions
diff --git a/Makefile.am b/Makefile.am index 031a485..6123343 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,6 +17,8 @@ odr_padenc_SOURCES = \ src/pad_common.h \ src/dls.cpp \ src/dls.h \ + src/sls.cpp \ + src/sls.h \ src/charset.cpp \ src/charset.h \ src/crc.cpp \ diff --git a/src/odr-padenc.cpp b/src/odr-padenc.cpp index 87fadb2..866b93e 100644 --- a/src/odr-padenc.cpp +++ b/src/odr-padenc.cpp @@ -31,283 +31,19 @@ #include <stdlib.h> #include <string> -#include <sstream> -#include <iostream> -#include <fstream> -#include <algorithm> #include <list> #include <sys/types.h> -#include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <getopt.h> #include "pad_common.h" #include "dls.h" - -#if HAVE_MAGICKWAND -# include <wand/magick_wand.h> -#endif - +#include "sls.h" static const int SLEEPDELAY_DEFAULT = 10; // seconds -static const size_t MAXSEGLEN = 8189; // Bytes (EN 301 234 v2.1.1, ch. 5.1.1) -static const size_t MAXSLIDESIZE = 51200; // Bytes (TS 101 499 v3.1.1, ch. 9.1.2) - -static const int MAXSLIDEID = 9999; // Roll-over value for fidx -static const size_t MAXHISTORYLEN = 50; // How many slides to keep in history -static const int MINQUALITY = 40; // Do not allow the image compressor to go below JPEG quality 40 - - -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(PADPacketizer& pad_packetizer, const std::string& fname, int fidx, bool raw_slides); - -uint8_vector_t createMotHeader( - size_t blobsize, - int fidx, - bool jfif_not_png, - const std::string ¶ms_fname); - -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); - -DATA_GROUP* packMscDG(MSCDG* msc); - - -// MOT Slideshow related -static int cindex_header = 0; -static int cindex_body = 0; - -static const std::string SLS_PARAMS_SUFFIX = ".sls_params"; - - -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));} - - void AddExtensionFixedSize(int pli, int param_id, const uint8_t* data_field, size_t data_field_len); - void AddExtensionVarSize(int param_id, const uint8_t* data_field, size_t data_field_len); -public: - MOTHeader(size_t body_size, int content_type, int content_subtype); - - void AddExtension(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::AddExtensionFixedSize(int pli, int param_id, const uint8_t* data_field, size_t data_field_len) { - AddParamHeader(pli, param_id); - - for (size_t i = 0; i < data_field_len; i++) - data.push_back(data_field[i]); - - IncrementHeaderSize(1 + data_field_len); -} - -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); -} - -void MOTHeader::AddExtension(int param_id, const uint8_t* data_field, size_t data_field_len) { - int pli; - - switch(data_field_len) { - case 0: - pli = 0b00; - break; - case 1: - pli = 0b01; - break; - case 4: - pli = 0b10; - break; - default: - pli = 0b11; - break; - } - - if (pli == 0b11) - AddExtensionVarSize(param_id, data_field, data_field_len); - else - AddExtensionFixedSize(pli, param_id, data_field, data_field_len); -} - void usage(char* name) @@ -511,9 +247,10 @@ int main(int argc, char *argv[]) PADPacketizer pad_packetizer(padlen); DLSManager dls_manager(&pad_packetizer); + SLSManager sls_manager; std::list<slide_metadata_t> slides_to_transmit; - History slides_history(MAXHISTORYLEN); + History slides_history(History::MAXHISTORYLEN); while(1) { if (sls_dir) { // slide + possibly DLS @@ -532,8 +269,8 @@ int main(int argc, char *argv[]) continue; // skip slide params files - if(slide.length() >= SLS_PARAMS_SUFFIX.length() && - slide.compare(slide.length() - SLS_PARAMS_SUFFIX.length(), SLS_PARAMS_SUFFIX.length(), SLS_PARAMS_SUFFIX) == 0) + if(slide.length() >= SLSManager::SLS_PARAMS_SUFFIX.length() && + slide.compare(slide.length() - SLSManager::SLS_PARAMS_SUFFIX.length(), SLSManager::SLS_PARAMS_SUFFIX.length(), SLSManager::SLS_PARAMS_SUFFIX) == 0) continue; // add slide @@ -573,7 +310,7 @@ int main(int argc, char *argv[]) it != slides_to_transmit.cend(); ++it) { - ret = encodeFile(pad_packetizer, it->filepath, it->fidx, raw_slides); + ret = sls_manager.encodeFile(pad_packetizer, it->filepath, it->fidx, raw_slides); if (ret != 1) fprintf(stderr, "ODR-PadEnc Error: cannot encode file '%s'\n", it->filepath.c_str()); @@ -613,582 +350,3 @@ int main(int argc, char *argv[]) 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(PADPacketizer& pad_packetizer, 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 (verbose) { - fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d). Raw file: %zu Bytes\n", - fname.c_str(), fidx, blobsize); - } - - 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, fname + SLS_PARAMS_SUFFIX); - // 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.AddDG(dgli, false); - pad_packetizer.AddDG(mscdg, false); - - 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.AddDG(dgli, false); - pad_packetizer.AddDG(mscdg, false); - } - - ret = 1; - } - -encodefile_out: -#if HAVE_MAGICKWAND - if (m_wand) { - m_wand = DestroyMagickWand(m_wand); - } -#endif - - if (blob) { - free(blob); - } - return ret; -} - - -bool parse_sls_param_id(const std::string &key, const std::string &value, uint8_t &target) { - int value_int = atoi(value.c_str()); - if (value_int >= 0x00 && value_int <= 0xFF) { - target = value_int; - return true; - } - fprintf(stderr, "ODR-PadEnc Warning: SLS parameter '%s' %d out of range - ignored\n", key.c_str(), value_int); - return false; -} - - -bool check_sls_param_len(const std::string &key, size_t len, size_t len_max) { - if (len <= len_max) - return true; - fprintf(stderr, "ODR-PadEnc Warning: SLS parameter '%s' exceeds its maximum length (%zu > %zu) - ignored\n", key.c_str(), len, len_max); - return false; -} - - -void process_mot_params_file(MOTHeader& header, const std::string ¶ms_fname) { - std::ifstream params_fstream(params_fname); - if (!params_fstream.is_open()) - return; - - std::string line; - while (std::getline(params_fstream, line)) { - // 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: SLS 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, "process_mot_params_file: key: '%s', value: '%s'\n", key.c_str(), value.c_str()); -#endif - - if (key == "CategoryID/SlideID") { - // split value - std::vector<std::string> params = split_string(value, ' '); - if (params.size() != 2) { - fprintf(stderr, "ODR-PadEnc Warning: SLS parameter CategoryID/SlideID value '%s' does not have two parts - ignored\n", value.c_str()); - continue; - } - - uint8_t id_param[2]; - if (parse_sls_param_id("CategoryID", params[0], id_param[0]) & - parse_sls_param_id("SlideID", params[1], id_param[1])) { - header.AddExtension(0x25, id_param, sizeof(id_param)); - if (verbose) - fprintf(stderr, "ODR-PadEnc SLS parameter: CategoryID = %d / SlideID = %d\n", id_param[0], id_param[1]); - } - continue; - } - if (key == "CategoryTitle") { - if(!check_sls_param_len("CategoryTitle", value.length(), 128)) - continue; - - header.AddExtension(0x26, (uint8_t*) value.c_str(), value.length()); - if (verbose) - fprintf(stderr, "ODR-PadEnc SLS parameter: CategoryTitle = '%s'\n", value.c_str()); - continue; - } - if (key == "ClickThroughURL") { - if(!check_sls_param_len("ClickThroughURL", value.length(), 512)) - continue; - - header.AddExtension(0x27, (uint8_t*) value.c_str(), value.length()); - if (verbose) - fprintf(stderr, "ODR-PadEnc SLS parameter: ClickThroughURL = '%s'\n", value.c_str()); - continue; - } - if (key == "AlternativeLocationURL") { - if(!check_sls_param_len("AlternativeLocationURL", value.length(), 512)) - continue; - - header.AddExtension(0x28, (uint8_t*) value.c_str(), value.length()); - if (verbose) - fprintf(stderr, "ODR-PadEnc SLS parameter: AlternativeLocationURL = '%s'\n", value.c_str()); - continue; - } - - fprintf(stderr, "ODR-PadEnc Warning: SLS parameter '%s' unknown - ignored\n", key.c_str()); - } -} - - -uint8_vector_t createMotHeader(size_t blobsize, int fidx, bool jfif_not_png, const std::string ¶ms_fname) -{ - // prepare ContentName - uint8_t cntemp[10]; // = 1 + 8 + 1 = charset + name + terminator - cntemp[0] = (uint8_t) DABCharset::COMPLETE_EBU_LATIN << 4; - snprintf((char*) (cntemp + 1), sizeof(cntemp) - 1, "%04d.%s", fidx, jfif_not_png ? "jpg" : "png"); - - // MOT header - content type: image, content subtype: JFIF / PNG - MOTHeader header(blobsize, 0x02, jfif_not_png ? 0x001 : 0x003); - - // TriggerTime: NOW - uint8_t triggertime_now[4] = {0x00}; - header.AddExtension(0x05, triggertime_now, sizeof(triggertime_now)); - - // ContentName: XXXX.jpg / XXXX.png - header.AddExtension(0x0C, cntemp, sizeof(cntemp) - 1); // omit terminator - - // process params file if present - process_mot_params_file(header, params_fname); - - if (verbose) - fprintf(stderr, "ODR-PadEnc writing image as '%s'\n", cntemp + 1); - - 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; -} - - -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; -} diff --git a/src/pad_common.cpp b/src/pad_common.cpp index 7cc422d..e29d548 100644 --- a/src/pad_common.cpp +++ b/src/pad_common.cpp @@ -297,3 +297,17 @@ pad_t* PADPacketizer::FlushPAD() { ResetPAD(); return result; } + +DATA_GROUP* PADPacketizer::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; +} diff --git a/src/pad_common.h b/src/pad_common.h index 71e7b76..a2f836c 100644 --- a/src/pad_common.h +++ b/src/pad_common.h @@ -123,6 +123,8 @@ public: // will be removed, when pull (instead of push) approach is implemented! void WriteAllPADs(int output_fd, int limit = -1); + + static DATA_GROUP* CreateDataGroupLengthIndicator(size_t len); }; #endif /* PAD_COMMON_H_ */ diff --git a/src/sls.cpp b/src/sls.cpp new file mode 100644 index 0000000..024e664 --- /dev/null +++ b/src/sls.cpp @@ -0,0 +1,692 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/*! + \file sls.cpp + \brief Slideshow related code + + \author Sergio Sagliocco <sergio.sagliocco@csp.it> + \author Matthias P. Braendli <matthias@mpb.li> + \author Stefan Pöschel <odr@basicmaster.de> +*/ + +#include "sls.h" + + +// --- History ----------------------------------------------------------------- +const size_t History::MAXHISTORYLEN = 50; // How many slides to keep in history + +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 > SLSManager::MAXSLIDEID) { + m_last_given_fidx = 0; + } + + add(fp); + } + + return idx; +} + + +// --- MOTHeader ----------------------------------------------------------------- +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::AddExtensionFixedSize(int pli, int param_id, const uint8_t* data_field, size_t data_field_len) { + AddParamHeader(pli, param_id); + + for (size_t i = 0; i < data_field_len; i++) + data.push_back(data_field[i]); + + IncrementHeaderSize(1 + data_field_len); +} + +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); +} + +void MOTHeader::AddExtension(int param_id, const uint8_t* data_field, size_t data_field_len) { + int pli; + + switch(data_field_len) { + case 0: + pli = 0b00; + break; + case 1: + pli = 0b01; + break; + case 4: + pli = 0b10; + break; + default: + pli = 0b11; + break; + } + + if (pli == 0b11) + AddExtensionVarSize(param_id, data_field, data_field_len); + else + AddExtensionFixedSize(pli, param_id, data_field, data_field_len); +} + + +// --- SLSManager ----------------------------------------------------------------- +const size_t SLSManager::MAXSEGLEN = 8189; // Bytes (EN 301 234 v2.1.1, ch. 5.1.1) +const size_t SLSManager::MAXSLIDESIZE = 51200; // Bytes (TS 101 499 v3.1.1, ch. 9.1.2) +const int SLSManager::MAXSLIDEID = 9999; // Roll-over value for fidx +const int SLSManager::MINQUALITY = 40; // Do not allow the image compressor to go below JPEG quality 40 +const std::string SLSManager::SLS_PARAMS_SUFFIX = ".sls_params"; + + + +void SLSManager::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 SLSManager::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 SLSManager::encodeFile(PADPacketizer& pad_packetizer, 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 (verbose) { + fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d). Raw file: %zu Bytes\n", + fname.c_str(), fidx, blobsize); + } + + 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, fname + SLS_PARAMS_SUFFIX); + // 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 = PADPacketizer::CreateDataGroupLengthIndicator(mscdg->data.size()); + + pad_packetizer.AddDG(dgli, false); + pad_packetizer.AddDG(mscdg, false); + + 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 = PADPacketizer::CreateDataGroupLengthIndicator(mscdg->data.size()); + + pad_packetizer.AddDG(dgli, false); + pad_packetizer.AddDG(mscdg, false); + } + + ret = 1; + } + +encodefile_out: +#if HAVE_MAGICKWAND + if (m_wand) { + m_wand = DestroyMagickWand(m_wand); + } +#endif + + if (blob) { + free(blob); + } + return ret; +} + + +bool SLSManager::parse_sls_param_id(const std::string &key, const std::string &value, uint8_t &target) { + int value_int = atoi(value.c_str()); + if (value_int >= 0x00 && value_int <= 0xFF) { + target = value_int; + return true; + } + fprintf(stderr, "ODR-PadEnc Warning: SLS parameter '%s' %d out of range - ignored\n", key.c_str(), value_int); + return false; +} + + +bool SLSManager::check_sls_param_len(const std::string &key, size_t len, size_t len_max) { + if (len <= len_max) + return true; + fprintf(stderr, "ODR-PadEnc Warning: SLS parameter '%s' exceeds its maximum length (%zu > %zu) - ignored\n", key.c_str(), len, len_max); + return false; +} + + +void SLSManager::process_mot_params_file(MOTHeader& header, const std::string ¶ms_fname) { + std::ifstream params_fstream(params_fname); + if (!params_fstream.is_open()) + return; + + std::string line; + while (std::getline(params_fstream, line)) { + // 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: SLS 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, "process_mot_params_file: key: '%s', value: '%s'\n", key.c_str(), value.c_str()); +#endif + + if (key == "CategoryID/SlideID") { + // split value + std::vector<std::string> params = split_string(value, ' '); + if (params.size() != 2) { + fprintf(stderr, "ODR-PadEnc Warning: SLS parameter CategoryID/SlideID value '%s' does not have two parts - ignored\n", value.c_str()); + continue; + } + + uint8_t id_param[2]; + if (parse_sls_param_id("CategoryID", params[0], id_param[0]) & + parse_sls_param_id("SlideID", params[1], id_param[1])) { + header.AddExtension(0x25, id_param, sizeof(id_param)); + if (verbose) + fprintf(stderr, "ODR-PadEnc SLS parameter: CategoryID = %d / SlideID = %d\n", id_param[0], id_param[1]); + } + continue; + } + if (key == "CategoryTitle") { + if(!check_sls_param_len("CategoryTitle", value.length(), 128)) + continue; + + header.AddExtension(0x26, (uint8_t*) value.c_str(), value.length()); + if (verbose) + fprintf(stderr, "ODR-PadEnc SLS parameter: CategoryTitle = '%s'\n", value.c_str()); + continue; + } + if (key == "ClickThroughURL") { + if(!check_sls_param_len("ClickThroughURL", value.length(), 512)) + continue; + + header.AddExtension(0x27, (uint8_t*) value.c_str(), value.length()); + if (verbose) + fprintf(stderr, "ODR-PadEnc SLS parameter: ClickThroughURL = '%s'\n", value.c_str()); + continue; + } + if (key == "AlternativeLocationURL") { + if(!check_sls_param_len("AlternativeLocationURL", value.length(), 512)) + continue; + + header.AddExtension(0x28, (uint8_t*) value.c_str(), value.length()); + if (verbose) + fprintf(stderr, "ODR-PadEnc SLS parameter: AlternativeLocationURL = '%s'\n", value.c_str()); + continue; + } + + fprintf(stderr, "ODR-PadEnc Warning: SLS parameter '%s' unknown - ignored\n", key.c_str()); + } +} + + +uint8_vector_t SLSManager::createMotHeader(size_t blobsize, int fidx, bool jfif_not_png, const std::string ¶ms_fname) +{ + // prepare ContentName + uint8_t cntemp[10]; // = 1 + 8 + 1 = charset + name + terminator + cntemp[0] = (uint8_t) DABCharset::COMPLETE_EBU_LATIN << 4; + snprintf((char*) (cntemp + 1), sizeof(cntemp) - 1, "%04d.%s", fidx, jfif_not_png ? "jpg" : "png"); + + // MOT header - content type: image, content subtype: JFIF / PNG + MOTHeader header(blobsize, 0x02, jfif_not_png ? 0x001 : 0x003); + + // TriggerTime: NOW + uint8_t triggertime_now[4] = {0x00}; + header.AddExtension(0x05, triggertime_now, sizeof(triggertime_now)); + + // ContentName: XXXX.jpg / XXXX.png + header.AddExtension(0x0C, cntemp, sizeof(cntemp) - 1); // omit terminator + + // process params file if present + process_mot_params_file(header, params_fname); + + if (verbose) + fprintf(stderr, "ODR-PadEnc writing image as '%s'\n", cntemp + 1); + + return header.GetData(); +} + + +void SLSManager::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* SLSManager::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; +} diff --git a/src/sls.h b/src/sls.h new file mode 100644 index 0000000..69fc034 --- /dev/null +++ b/src/sls.h @@ -0,0 +1,230 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/*! + \file sls.h + \brief Slideshow related code + + \author Sergio Sagliocco <sergio.sagliocco@csp.it> + \author Matthias P. Braendli <matthias@mpb.li> + \author Stefan Pöschel <odr@basicmaster.de> +*/ + +#ifndef SLS_H_ +#define SLS_H_ + +#include "common.h" +#include "pad_common.h" + +#if HAVE_MAGICKWAND +# include <wand/magick_wand.h> +#endif + +#include <sys/stat.h> +#include <deque> +#include <fstream> +#include <iostream> +#include <algorithm> + + +// --- MSCDG ----------------------------------------------------------------- +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 +}; + + +// --- slide_metadata_t ----------------------------------------------------------------- +/*! 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; + } +}; + + +// --- fingerprint_t ----------------------------------------------------------------- +/*! 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; + } +}; + + +// --- History ----------------------------------------------------------------- +/*! 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: + static const size_t MAXHISTORYLEN; + + 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); +}; + + +// --- MOTHeader ----------------------------------------------------------------- +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));} + + void AddExtensionFixedSize(int pli, int param_id, const uint8_t* data_field, size_t data_field_len); + void AddExtensionVarSize(int param_id, const uint8_t* data_field, size_t data_field_len); +public: + MOTHeader(size_t body_size, int content_type, int content_subtype); + + void AddExtension(int param_id, const uint8_t* data_field, size_t data_field_len); + const uint8_vector_t GetData() {return data;} +}; + + +// --- SLSManager ----------------------------------------------------------------- +class SLSManager { +private: + void warnOnSmallerImage(size_t height, size_t width, const std::string& fname); +#if HAVE_MAGICKWAND + size_t resizeImage(MagickWand* m_wand, unsigned char** blob, const std::string& fname, bool* jfif_not_png); +#endif + bool parse_sls_param_id(const std::string &key, const std::string &value, uint8_t &target); + bool check_sls_param_len(const std::string &key, size_t len, size_t len_max); + void process_mot_params_file(MOTHeader& header, const std::string ¶ms_fname); + uint8_vector_t createMotHeader(size_t blobsize, int fidx, bool jfif_not_png, const std::string ¶ms_fname); + 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); + DATA_GROUP* packMscDG(MSCDG* msc); + + int cindex_header; + int cindex_body; +public: + static const size_t MAXSEGLEN; + static const size_t MAXSLIDESIZE; + static const int MAXSLIDEID; + static const int MINQUALITY; + static const std::string SLS_PARAMS_SUFFIX; + + SLSManager() : cindex_header(0), cindex_body(0) {} + + int encodeFile(PADPacketizer& pad_packetizer, const std::string& fname, int fidx, bool raw_slides); +}; + +#endif /* SLS_H_ */ |