aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2016-09-02 17:43:32 +0200
committerMatthias P. Braendli <matthias.braendli@mpb.li>2016-09-07 21:20:24 +0200
commit4809f3c042a99a639542b1e6cd22657871113260 (patch)
treedc0ec0f256eaceb79d63daa09591d4a6ef08cb73 /src
parent5542c8ecff635f9abc05b6b2ff255db02c2c9c46 (diff)
downloadetisnoop-4809f3c042a99a639542b1e6cd22657871113260.tar.gz
etisnoop-4809f3c042a99a639542b1e6cd22657871113260.tar.bz2
etisnoop-4809f3c042a99a639542b1e6cd22657871113260.zip
Move all sources to src/
Diffstat (limited to 'src')
-rw-r--r--src/dabplussnoop.cpp324
-rw-r--r--src/dabplussnoop.hpp133
-rw-r--r--src/etiinput.cpp198
-rw-r--r--src/etiinput.hpp49
-rw-r--r--src/etisnoop.cpp805
-rw-r--r--src/faad_decoder.cpp177
-rw-r--r--src/faad_decoder.hpp100
-rw-r--r--src/fig0_0.cpp62
-rw-r--r--src/fig0_1.cpp112
-rw-r--r--src/fig0_10.cpp74
-rw-r--r--src/fig0_11.cpp209
-rw-r--r--src/fig0_13.cpp131
-rw-r--r--src/fig0_14.cpp81
-rw-r--r--src/fig0_16.cpp127
-rw-r--r--src/fig0_17.cpp138
-rw-r--r--src/fig0_18.cpp112
-rw-r--r--src/fig0_19.cpp105
-rw-r--r--src/fig0_2.cpp168
-rw-r--r--src/fig0_21.cpp323
-rw-r--r--src/fig0_22.cpp186
-rw-r--r--src/fig0_24.cpp109
-rw-r--r--src/fig0_25.cpp112
-rw-r--r--src/fig0_26.cpp109
-rw-r--r--src/fig0_27.cpp104
-rw-r--r--src/fig0_28.cpp90
-rw-r--r--src/fig0_3.cpp110
-rw-r--r--src/fig0_31.cpp133
-rw-r--r--src/fig0_5.cpp114
-rw-r--r--src/fig0_6.cpp190
-rw-r--r--src/fig0_8.cpp147
-rw-r--r--src/fig0_9.cpp133
-rw-r--r--src/fig1.cpp157
-rw-r--r--src/figs.cpp114
-rw-r--r--src/figs.hpp113
-rw-r--r--src/firecode.c50
-rw-r--r--src/firecode.h33
-rw-r--r--src/lib_crc.c459
-rw-r--r--src/lib_crc.h66
-rw-r--r--src/repetitionrate.cpp196
-rw-r--r--src/repetitionrate.hpp47
-rw-r--r--src/rsdecoder.cpp83
-rw-r--r--src/rsdecoder.hpp43
-rw-r--r--src/tables.cpp311
-rw-r--r--src/tables.hpp43
-rw-r--r--src/utils.cpp154
-rw-r--r--src/utils.hpp57
-rw-r--r--src/watermarkdecoder.hpp98
-rw-r--r--src/wavfile.c82
-rw-r--r--src/wavfile.h17
49 files changed, 7088 insertions, 0 deletions
diff --git a/src/dabplussnoop.cpp b/src/dabplussnoop.cpp
new file mode 100644
index 0000000..cd91d49
--- /dev/null
+++ b/src/dabplussnoop.cpp
@@ -0,0 +1,324 @@
+/*
+ Copyright (C) 2014, 2015 Matthias P. Braendli (http://www.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/>.
+
+ dabplussnoop.cpp
+ Parse DAB+ frames from a ETI file
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+ Mathias Coinchon <coinchon@yahoo.com>
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <cassert>
+#include <sstream>
+#include <algorithm>
+#include <vector>
+#include "dabplussnoop.hpp"
+extern "C" {
+#include "firecode.h"
+#include "lib_crc.h"
+}
+#include "faad_decoder.hpp"
+#include "rsdecoder.hpp"
+
+#define DPS_INDENT "\t\t"
+#define DPS_PREFIX "DAB+ decode:"
+
+#define DPS_DEBUG 0
+
+using namespace std;
+
+void DabPlusSnoop::push(uint8_t* streamdata, size_t streamsize)
+{
+ // Try to decode audio
+ size_t original_size = m_data.size();
+ m_data.resize(original_size + streamsize);
+ memcpy(&m_data[original_size], streamdata, streamsize);
+
+ if (seek_valid_firecode()) {
+ // m_data now points to a valid header
+
+ if (decode()) {
+
+ // First dump to subchannel file (superframe+parity word)
+ if (m_raw_data_stream_fd == NULL) {
+ stringstream dump_filename;
+ dump_filename << "stream-" << m_index << ".msc";
+
+ m_raw_data_stream_fd = fopen(dump_filename.str().c_str(), "w");
+
+ if (m_raw_data_stream_fd == NULL) {
+ perror("File open failed");
+ }
+ }
+
+ fwrite(&m_data[0], m_subchannel_index, 120, m_raw_data_stream_fd);
+
+ // We have been able to decode the AUs, now flush vector
+ m_data.clear();
+ }
+ }
+}
+
+// Idea and some code taken from Xpadxpert
+bool DabPlusSnoop::seek_valid_firecode()
+{
+ if (m_data.size() < 10) {
+ // Not enough data
+ return false;
+ }
+
+ bool crc_ok = false;
+ size_t i;
+
+ for (i = 0; i < m_data.size() - 10; i++) {
+ uint8_t* b = &m_data[i];
+
+ // the three bytes after the firecode must not be zero
+ // (simple plausibility check to avoid sync in zero byte region)
+ if (b[3] != 0x00 || (b[4] & 0xF0) != 0x00) {
+ uint16_t header_firecode = (b[0] << 8) | b[1];
+ uint16_t calculated_firecode = firecode_crc(b+2, 9);
+
+ if (header_firecode == calculated_firecode) {
+ crc_ok = true;
+ break;
+ }
+ }
+ }
+
+ if (crc_ok) {
+#if DPS_DEBUG
+ printf(DPS_PREFIX " Found valid FireCode at %zu\n", i);
+#endif
+ //erase elements before the header
+ m_data.erase(m_data.begin(), m_data.begin() + i);
+ return true;
+ }
+ else {
+#if DPS_DEBUG
+ printf(DPS_PREFIX " No valid FireCode found\n");
+#endif
+
+ m_data.clear();
+ return false;
+ }
+}
+
+bool DabPlusSnoop::decode()
+{
+#if DPS_DEBUG
+ printf(DPS_PREFIX " We have %zu bytes of data\n", m_data.size());
+#endif
+
+ const size_t sf_len = m_subchannel_index * 120;
+ if (m_subchannel_index && m_data.size() >= sf_len) {
+ std::vector<uint8_t> b(sf_len);
+ std::copy(m_data.begin(), m_data.begin() + sf_len, b.begin());
+
+ RSDecoder rs_dec;
+ int rs_errors = rs_dec.DecodeSuperframe(b, m_subchannel_index);
+
+ if (rs_errors == -1) {
+ return false;
+ }
+
+ // -- Parse he_aac_super_frame
+ // ---- Parse he_aac_super_frame_header
+ // ------ Parse firecode and audio params
+ uint16_t header_firecode = (b[0] << 8) | b[1];
+ uint8_t audio_params = b[2];
+ int rfa = (audio_params & 0x80) ? true : false;
+ m_dac_rate = (audio_params & 0x40) ? true : false;
+ m_sbr_flag = (audio_params & 0x20) ? true : false;
+ m_aac_channel_mode = (audio_params & 0x10) ? true : false;
+ m_ps_flag = (audio_params & 0x08) ? true : false;
+ m_mpeg_surround_config = (audio_params & 0x07);
+
+ int num_aus;
+ if (!m_dac_rate && m_sbr_flag) num_aus = 2;
+ // AAC core sampling rate 16 kHz
+ else if (m_dac_rate && m_sbr_flag) num_aus = 3;
+ // AAC core sampling rate 24 kHz
+ else if (!m_dac_rate && !m_sbr_flag) num_aus = 4;
+ // AAC core sampling rate 32 kHz
+ else if (m_dac_rate && !m_sbr_flag) num_aus = 6;
+ // AAC core sampling rate 48 kHz
+
+#if DPS_DEBUG
+ printf( DPS_INDENT DPS_PREFIX "\n"
+ DPS_INDENT "\tfirecode 0x%x\n"
+ DPS_INDENT "\trfa %d\n"
+ DPS_INDENT "\tdac_rate %d\n"
+ DPS_INDENT "\tsbr_flag %d\n"
+ DPS_INDENT "\taac_channel_mode %d\n"
+ DPS_INDENT "\tps_flag %d\n"
+ DPS_INDENT "\tmpeg_surround_config %d\n"
+ DPS_INDENT "\tnum_aus %d\n",
+ header_firecode, rfa, m_dac_rate, m_sbr_flag,
+ m_aac_channel_mode, m_ps_flag, m_mpeg_surround_config,
+ num_aus);
+#else
+ // Avoid "unused variable" warning
+ (void)header_firecode;
+ (void)rfa;
+#endif
+
+
+ // ------ Parse au_start
+ auto au_starts = b.begin() + 3;
+
+ vector<uint8_t> au_start_nibbles(0);
+
+ /* Each AU_START is encoded in three nibbles.
+ * When we have n AUs, we have n-1 au_start values. */
+ for (int i = 0; i < (num_aus-1)*3; i++) {
+ if (i % 2 == 0) {
+ char nibble = au_starts[i/2] >> 4;
+
+ au_start_nibbles.push_back( nibble );
+ }
+ else {
+ char nibble = au_starts[i/2] & 0x0F;
+
+ au_start_nibbles.push_back( nibble );
+ }
+
+ }
+
+
+ vector<int> au_start(num_aus);
+
+ if (num_aus == 2)
+ au_start[0] = 5;
+ else if (num_aus == 3)
+ au_start[0] = 6;
+ else if (num_aus == 4)
+ au_start[0] = 8;
+ else if (num_aus == 6)
+ au_start[0] = 11;
+
+
+ int nib = 0;
+ for (int au = 1; au < num_aus; au++) {
+ au_start[au] = au_start_nibbles[nib] << 8 | \
+ au_start_nibbles[nib+1] << 4 | \
+ au_start_nibbles[nib+2];
+
+ nib += 3;
+ }
+
+#if DPS_DEBUG
+ printf(DPS_INDENT DPS_PREFIX " AU start\n");
+ for (int au = 0; au < num_aus; au++) {
+ printf(DPS_INDENT "\tAU[%d] %d 0x%x\n", au,
+ au_start[au],
+ au_start[au]);
+ }
+#endif
+
+ return extract_au(au_start);
+ }
+ else {
+ return false;
+ }
+}
+
+bool DabPlusSnoop::extract_au(vector<int> au_start)
+{
+ vector<vector<uint8_t> > aus(au_start.size());
+
+ // The last entry of au_start must the end of valid
+ // AU data. We stop at m_subchannel_index * 110 because
+ // what comes after is RS parity
+ au_start.push_back(m_subchannel_index * 110);
+
+ bool all_crc_ok = true;
+
+ for (size_t au = 0; au < aus.size(); au++)
+ {
+#if DPS_DEBUG
+ printf(DPS_PREFIX DPS_INDENT
+ "Copy au %zu of size %zu\n",
+ au,
+ au_start[au+1] - au_start[au]-2 );
+#endif
+
+ aus[au].resize(au_start[au+1] - au_start[au]-2);
+ std::copy(
+ m_data.begin() + au_start[au],
+ m_data.begin() + au_start[au+1]-2,
+ aus[au].begin() );
+
+ /* Check CRC */
+ uint16_t au_crc = m_data[au_start[au+1]-2] << 8 | \
+ m_data[au_start[au+1]-1];
+
+ uint16_t calc_crc = 0xFFFF;
+ for (vector<uint8_t>::iterator au_data = aus[au].begin();
+ au_data != aus[au].end();
+ ++au_data) {
+ calc_crc = update_crc_ccitt(calc_crc, *au_data);
+ }
+ calc_crc =~ calc_crc;
+
+ if (calc_crc != au_crc) {
+ printf(DPS_INDENT DPS_PREFIX
+ "Erroneous CRC for au %zu: 0x%04x vs 0x%04x\n",
+ au, calc_crc, au_crc);
+
+ all_crc_ok = false;
+ }
+ }
+
+ if (all_crc_ok) {
+ return analyse_au(aus);
+ }
+ else {
+ //discard faulty superframe (to be improved to correct/conceal)
+ m_data.clear();
+ return false;
+ }
+}
+
+bool DabPlusSnoop::analyse_au(vector<vector<uint8_t> >& aus)
+{
+ stringstream ss_filename;
+
+ ss_filename << "stream-" << m_index;
+
+ if (!m_faad_decoder.is_initialised()) {
+ m_faad_decoder.open(ss_filename.str(), m_ps_flag,
+ m_aac_channel_mode, m_dac_rate, m_sbr_flag,
+ m_mpeg_surround_config);
+ }
+
+ return m_faad_decoder.decode(aus);
+}
+
+void DabPlusSnoop::close()
+{
+ m_faad_decoder.close();
+
+ if (m_raw_data_stream_fd) {
+ fclose(m_raw_data_stream_fd);
+ }
+}
diff --git a/src/dabplussnoop.hpp b/src/dabplussnoop.hpp
new file mode 100644
index 0000000..865a35e
--- /dev/null
+++ b/src/dabplussnoop.hpp
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2014 Matthias P. Braendli (http://www.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/>.
+
+ dabplussnoop.cpp
+ Parse DAB+ frames from a ETI file
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+*/
+
+/* From the spec:
+
+subchannel_index = MSC sub-channel size (kbps) / 8
+audio_super_frame_size (bytes) = subchannel_index * 110
+
+// Derived from
+// au_start[n] = au_start[n - 1] + au_size[n - 1] + 2;
+// 2 bytes for CRC
+au_size[n] = au_start[n+1] - au_start[n] - 2;
+
+he_aac_super_frame(subchannel_index)
+{
+ he_aac_super_frame_header()
+ {
+ header_firecode 16
+ rfa 1
+ dac_rate 1
+ sbr_flag 1
+ aac_channel_mode 1
+ ps_flag 1
+ mpeg_surround_config 3
+
+ // end of audio parameters
+ if ((dac_rate == 0) && (sbr_flag == 1)) num_aus = 2;
+ // AAC core sampling rate 16 kHz
+ if ((dac_rate == 1) && (sbr_flag == 1)) num_aus = 3;
+ // AAC core sampling rate 24 kHz
+ if ((dac_rate == 0) && (sbr_flag == 0)) num_aus = 4;
+ // AAC core sampling rate 32 kHz
+ if ((dac_rate == 1) && (sbr_flag == 0)) num_aus = 6;
+ // AAC core sampling rate 48 kHz
+
+ for (n = 1; n < num_aus; n++) {
+ au_start[n]; 12
+ }
+
+ if !((dac_rate == 1) && (sbr_flag == 1))
+ alignment 4
+ }
+
+ for (n = 0; n < num_aus; n++) {
+ au[n] 8 * au_size[n]
+ au_crc[n] 16
+ }
+
+}
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sstream>
+#include <vector>
+#include "faad_decoder.hpp"
+
+#ifndef __DABPLUSSNOOP_H_
+#define __DABPLUSSNOOP_H_
+
+
+class DabPlusSnoop
+{
+ public:
+ DabPlusSnoop() :
+ m_index(0),
+ m_subchannel_index(0),
+ m_data(0),
+ m_raw_data_stream_fd(NULL) {}
+
+ void set_subchannel_index(unsigned subchannel_index)
+ {
+ m_subchannel_index = subchannel_index;
+ }
+
+ void set_index(int index)
+ {
+ m_index = index;
+ }
+
+ void push(uint8_t* streamdata, size_t streamsize);
+
+ void close(void);
+
+ private:
+ /* Data needed for FAAD */
+ FaadDecoder m_faad_decoder;
+ int m_index;
+
+ bool m_ps_flag;
+ bool m_aac_channel_mode;
+ bool m_dac_rate;
+ bool m_sbr_flag;
+ int m_mpeg_surround_config;
+
+ /* Functions */
+
+ bool seek_valid_firecode(void);
+ bool decode(void);
+ bool extract_au(std::vector<int> au_start);
+ bool analyse_au(std::vector<std::vector<uint8_t> >& aus);
+
+ unsigned m_subchannel_index;
+ std::vector<uint8_t> m_data;
+
+ FILE* m_raw_data_stream_fd;
+};
+
+#endif
+
diff --git a/src/etiinput.cpp b/src/etiinput.cpp
new file mode 100644
index 0000000..8f47ec0
--- /dev/null
+++ b/src/etiinput.cpp
@@ -0,0 +1,198 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
+ Her Majesty the Queen in Right of Canada (Communications Research
+ Center Canada)
+
+ Copyrigth (C) 2014
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ Taken from ODR-DabMod
+
+ Supported file formats: RAW, FRAMED, STREAMED
+ Supports re-sync to RAW ETI file
+ */
+/*
+ This file is part of ODR-DabMod.
+
+ ODR-DabMod 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.
+
+ ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "etiinput.hpp"
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h> /* Definition of AT_* constants */
+#include <sys/stat.h>
+
+int identify_eti_format(FILE* inputFile, int *streamType)
+{
+ *streamType = ETI_STREAM_TYPE_NONE;
+
+ struct stat inputFileStat;
+ fstat(fileno(inputFile), &inputFileStat);
+ size_t inputfilelength_ = inputFileStat.st_size;
+
+ int nbframes_;
+
+ uint32_t sync;
+ uint32_t nbFrames;
+ uint16_t frameSize;
+
+ char discard_buffer[6144];
+
+ if (fread(&sync, sizeof(sync), 1, inputFile) != 1) {
+ fprintf(stderr, "Unable to read sync in input file!\n");
+ perror("");
+ return -1;
+ }
+ if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) {
+ *streamType = ETI_STREAM_TYPE_RAW;
+ if (inputfilelength_ > 0) {
+ nbframes_ = inputfilelength_ / 6144;
+ }
+ else {
+ nbframes_ = ~0;
+ }
+ if (fseek(inputFile, -sizeof(sync), SEEK_CUR) != 0) {
+ // if the seek fails, consume the rest of the frame
+ if (fread(discard_buffer, 6144 - sizeof(sync), 1, inputFile)
+ != 1) {
+ fprintf(stderr, "Unable to read from input file!\n");
+ perror("");
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ nbFrames = sync;
+ if (fread(&frameSize, sizeof(frameSize), 1, inputFile) != 1) {
+ fprintf(stderr, "Unable to read frame size in input file!\n");
+ perror("");
+ return -1;
+ }
+ sync >>= 16;
+ sync &= 0xffff;
+ sync |= ((uint32_t)frameSize) << 16;
+
+ if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) {
+ *streamType = ETI_STREAM_TYPE_STREAMED;
+ frameSize = nbFrames & 0xffff;
+ if (inputfilelength_ > 0) {
+ nbframes_ = inputfilelength_ / (frameSize + 2);
+ }
+ else {
+ nbframes_ = ~0;
+ }
+ if (fseek(inputFile, -6, SEEK_CUR) != 0) {
+ // if the seek fails, consume the rest of the frame
+ if (fread(discard_buffer, frameSize - 4, 1, inputFile)
+ != 1) {
+ fprintf(stderr, "Unable to read from input file!\n");
+ perror("");
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ if (fread(&sync, sizeof(sync), 1, inputFile) != 1) {
+ fprintf(stderr, "Unable to read nb frame in input file!\n");
+ perror("");
+ return -1;
+ }
+ if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) {
+ *streamType = ETI_STREAM_TYPE_FRAMED;
+ if (fseek(inputFile, -6, SEEK_CUR) != 0) {
+ // if the seek fails, consume the rest of the frame
+ if (fread(discard_buffer, frameSize - 4, 1, inputFile)
+ != 1) {
+ fprintf(stderr, "Unable to read from input file!\n");
+ perror("");
+ return -1;
+ }
+ }
+ nbframes_ = ~0;
+ return 0;
+ }
+
+ // Search for the sync marker byte by byte
+ for (size_t i = 10; i < 6144 + 10; ++i) {
+ sync >>= 8;
+ sync &= 0xffffff;
+ if (fread((uint8_t*)&sync + 3, 1, 1, inputFile) != 1) {
+ fprintf(stderr, "Unable to read from input file!\n");
+ perror("");
+ return -1;
+ }
+ if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) {
+ *streamType = ETI_STREAM_TYPE_RAW;
+ if (inputfilelength_ > 0) {
+ nbframes_ = (inputfilelength_ - i) / 6144;
+ }
+ else {
+ nbframes_ = ~0;
+ }
+ if (fseek(inputFile, -sizeof(sync), SEEK_CUR) != 0) {
+ if (fread(discard_buffer, 6144 - sizeof(sync), 1, inputFile)
+ != 1) {
+ fprintf(stderr, "Unable to read from input file!\n");
+ perror("");
+ return -1;
+ }
+ }
+ return 0;
+ }
+ }
+
+ (void)nbframes_; // suppress warning "nbframes_ unused"
+ fprintf(stderr, "Bad input file format!\n");
+ return -1;
+}
+
+int get_eti_frame(FILE* inputfile, int stream_type, void* buf)
+{
+ // Initialise buffer
+ memset(buf, 0x55, 6144);
+
+ uint16_t frameSize;
+ if (stream_type == ETI_STREAM_TYPE_RAW) {
+ frameSize = 6144;
+ }
+ else {
+ if (fread(&frameSize, sizeof(frameSize), 1, inputfile) != 1) {
+ // EOF
+ return 0;
+ }
+ }
+
+ if (frameSize > 6144) { // there might be a better limit
+ printf("Wrong frame size %u in ETI file!\n", frameSize);
+ return -1;
+ }
+
+ int read_bytes = fread(buf, 1, frameSize, inputfile);
+ if (read_bytes != frameSize) {
+ // A short read of a frame (i.e. reading an incomplete frame)
+ // is not tolerated. Input files must not contain incomplete frames
+ printf("Incomplete frame in ETI file!\n");
+ return -1;
+ }
+
+ // We have added padding, so we always return 6144 bytes
+ return 6144;
+}
+
diff --git a/src/etiinput.hpp b/src/etiinput.hpp
new file mode 100644
index 0000000..d8b7691
--- /dev/null
+++ b/src/etiinput.hpp
@@ -0,0 +1,49 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
+ Her Majesty the Queen in Right of Canada (Communications Research
+ Center Canada)
+
+ Copyrigth (C) 2014
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ Taken from ODR-DabMod
+
+ Supported file formats: RAW, FRAMED, STREAMED
+ Supports re-sync to RAW ETI file
+ */
+/*
+ This file is part of ODR-DabMod.
+
+ ODR-DabMod 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.
+
+ ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdio.h>
+
+#ifndef _ETIINPUT_H_
+#define _ETIINPUT_H_
+
+
+#define ETI_STREAM_TYPE_NONE 0
+#define ETI_STREAM_TYPE_RAW 1
+#define ETI_STREAM_TYPE_STREAMED 2
+#define ETI_STREAM_TYPE_FRAMED 3
+
+/* Identify the stream type, and return 0 on success, -1 on failure */
+int identify_eti_format(FILE* inputfile, int *stream_type);
+
+/* Read the next ETI frame into buf, which must be at least 6144 bytes big
+ * Return number of bytes read, or zero if EOF */
+int get_eti_frame(FILE* inputfile, int stream_type, void* buf);
+
+#endif
+
diff --git a/src/etisnoop.cpp b/src/etisnoop.cpp
new file mode 100644
index 0000000..92f54fd
--- /dev/null
+++ b/src/etisnoop.cpp
@@ -0,0 +1,805 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ 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/>.
+
+ etisnoop.cpp
+ Parse ETI NI G.703 file
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <cinttypes>
+#include <string>
+#include <vector>
+#include <map>
+#include <sstream>
+#include <time.h>
+extern "C" {
+#include "lib_crc.h"
+}
+#include "dabplussnoop.hpp"
+#include "utils.hpp"
+#include "etiinput.hpp"
+#include "figs.hpp"
+#include "watermarkdecoder.hpp"
+#include "repetitionrate.hpp"
+
+struct FIG
+{
+ int type;
+ int ext;
+ int len;
+};
+
+class FIGalyser
+{
+ public:
+ FIGalyser()
+ {
+ clear();
+ }
+
+ void set_fib(int fib)
+ {
+ m_fib = fib;
+ }
+
+ void push_back(int type, int ext, int len)
+ {
+ struct FIG fig = {
+ .type = type,
+ .ext = ext,
+ .len = len };
+
+ m_figs[m_fib].push_back(fig);
+ }
+
+ void analyse()
+ {
+ printf("FIC ");
+
+ for (size_t fib = 0; fib < m_figs.size(); fib++) {
+ int consumed = 7;
+ int fic_size = 0;
+ printf("[%1zu ", fib);
+
+ for (size_t i = 0; i < m_figs[fib].size(); i++) {
+ FIG &f = m_figs[fib][i];
+ printf("%01d/%02d (%2d) ", f.type, f.ext, f.len);
+
+ consumed += 10;
+
+ fic_size += f.len;
+ }
+
+ printf(" ");
+
+ int align = 60 - consumed;
+ if (align > 0) {
+ while (align--) {
+ printf(" ");
+ }
+ }
+
+ printf("|");
+
+ for (int i = 0; i < 15; i++) {
+ if (2*i < fic_size) {
+ printf("#");
+ }
+ else {
+ printf("-");
+ }
+ }
+
+ printf("| ] ");
+
+ }
+
+ printf("\n");
+ }
+
+ void clear()
+ {
+ m_figs.clear();
+ m_figs.resize(3);
+ }
+
+ private:
+ int m_fib;
+ std::vector<std::vector<FIG> > m_figs;
+};
+
+
+struct FIG0_13_shortAppInfo
+{
+ uint16_t SId;
+ uint8_t No:4;
+ uint8_t SCIdS:4;
+} PACKED;
+
+
+#define ETINIPACKETSIZE 6144
+
+using namespace std;
+
+struct eti_analyse_config_t {
+ eti_analyse_config_t() :
+ etifd(nullptr),
+ ignore_error(false),
+ streams_to_decode(),
+ analyse_fic_carousel(false),
+ analyse_fig_rates(false),
+ analyse_fig_rates_per_second(false),
+ decode_watermark(false) {}
+
+ FILE* etifd;
+ bool ignore_error;
+ std::map<int, DabPlusSnoop> streams_to_decode;
+ bool analyse_fic_carousel;
+ bool analyse_fig_rates;
+ bool analyse_fig_rates_per_second;
+ bool decode_watermark;
+};
+
+
+// Function prototypes
+void decodeFIG(FIGalyser &figs,
+ WatermarkDecoder &wm_decoder,
+ uint8_t* figdata,
+ uint8_t figlen,
+ uint16_t figtype,
+ int indent);
+
+int eti_analyse(eti_analyse_config_t& config);
+
+const char *get_programme_type_str(size_t int_table_Id, size_t pty);
+int sprintfMJD(char *dst, int mjd);
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+const struct option longopts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {"ignore-error", no_argument, 0, 'e'},
+ {"decode-stream", required_argument, 0, 'd'},
+ {"input", required_argument, 0, 'i'}
+};
+
+void usage(void)
+{
+ fprintf(stderr,
+ "Opendigitalradio ETISnoop analyser %s compiled at %s, %s\n\n"
+ "The ETISnoop analyser decodes and prints out a RAW ETI file in a\n"
+ "form that makes analysis easier.\n"
+ "\n"
+ " http://www.opendigitalradio.org\n"
+ "\n"
+ "Usage: etisnoop [-v] [-f] [-w] [-i filename] [-d stream_index]\n"
+ "\n"
+ " -v increase verbosity (can be given more than once)\n"
+ " -d N decode subchannel N into .msc and .wav files\n"
+ " -f analyse FIC carousel\n"
+ " -r analyse FIG rates in FIGs per second\n"
+ " -R analyse FIG rates in frames per FIG\n"
+ " -w decode CRC-DABMUX and ODR-DabMux watermark.\n",
+#if defined(GITVERSION)
+ GITVERSION,
+#else
+ VERSION,
+#endif
+ __DATE__, __TIME__);
+}
+
+int main(int argc, char *argv[])
+{
+ int index;
+ int ch = 0;
+ string file_name("-");
+
+ eti_analyse_config_t config;
+
+ while(ch != -1) {
+ ch = getopt_long(argc, argv, "d:efhrRvwi:", longopts, &index);
+ switch (ch) {
+ case 'd':
+ {
+ int subchix = atoi(optarg);
+ DabPlusSnoop dps;
+ config.streams_to_decode[subchix] = dps;
+ }
+ break;
+ case 'e':
+ config.ignore_error = true;
+ break;
+ case 'i':
+ file_name = optarg;
+ break;
+ case 'f':
+ config.analyse_fic_carousel = true;
+ break;
+ case 'r':
+ config.analyse_fig_rates = true;
+ config.analyse_fig_rates_per_second = true;
+ break;
+ case 'R':
+ config.analyse_fig_rates = true;
+ config.analyse_fig_rates_per_second = false;
+ break;
+ case 'v':
+ set_verbosity(get_verbosity() + 1);
+ break;
+ case 'w':
+ config.decode_watermark = true;
+ break;
+ case 'h':
+ usage();
+ return 1;
+ break;
+ }
+ }
+
+ FILE* etifd;
+
+ if (file_name == "-") {
+ printf("Analysing stdin\n");
+ etifd = stdin;
+ }
+ else {
+ etifd = fopen(file_name.c_str(), "r");
+ if (etifd == NULL) {
+ perror("File open failed");
+ return 1;
+ }
+ }
+ config.etifd = etifd;
+
+ eti_analyse(config);
+ fclose(etifd);
+}
+
+int eti_analyse(eti_analyse_config_t& config)
+{
+ uint8_t p[ETINIPACKETSIZE];
+ string desc;
+ char prevsync[3]={0x00,0x00,0x00};
+ uint8_t ficf,nst,fp,mid,ficl;
+ uint16_t fl,crch;
+ uint16_t crc;
+ uint8_t scid,tpl;
+ uint16_t sad[64],stl[64];
+ char sdesc[256];
+ uint32_t frame_nb = 0, frame_sec = 0, frame_ms = 0, frame_h, frame_m, frame_s;
+
+ bool running = true;
+
+ int stream_type = ETI_STREAM_TYPE_NONE;
+ if (identify_eti_format(config.etifd, &stream_type) == -1) {
+ printf("Could not identify stream type\n");
+
+ running = false;
+ }
+ else {
+ printf("Identified ETI type ");
+ if (stream_type == ETI_STREAM_TYPE_RAW)
+ printf("RAW\n");
+ else if (stream_type == ETI_STREAM_TYPE_STREAMED)
+ printf("STREAMED\n");
+ else if (stream_type == ETI_STREAM_TYPE_FRAMED)
+ printf("FRAMED\n");
+ else
+ printf("?\n");
+ }
+
+ WatermarkDecoder wm_decoder;
+
+ if (config.analyse_fig_rates) {
+ rate_display_header(config.analyse_fig_rates_per_second);
+ }
+
+ while (running) {
+
+ int ret = get_eti_frame(config.etifd, stream_type, p);
+ if (ret == -1) {
+ fprintf(stderr, "ETI file read error\n");
+ break;
+ }
+ else if (ret == 0) {
+ fprintf(stderr, "End of ETI\n");
+ break;
+ }
+
+ // Timestamp and Frame Number
+ frame_h = (frame_sec / 3600);
+ frame_m = (frame_sec - (frame_h * 3600)) / 60;
+ frame_s = (frame_sec - (frame_h * 3600) - (frame_m * 60));
+ sprintf(sdesc, "%02d:%02d:%02d.%03d frame %d", frame_h, frame_m, frame_s, frame_ms, frame_nb);
+ printbuf(sdesc, 0, NULL, 0);
+ frame_ms += 24; // + 24 ms
+ if (frame_ms >= 1000) {
+ frame_ms -= 1000;
+ frame_sec++;
+ }
+ frame_nb++;
+
+ // SYNC
+ printbuf("SYNC", 0, p, 4);
+
+ // SYNC - ERR
+ if (p[0] == 0xFF) {
+ desc = "No error";
+ printbuf("ERR", 1, p, 1, desc);
+ }
+ else {
+ desc = "Error";
+ printbuf("ERR", 1, p, 1, desc);
+ if (!config.ignore_error) {
+ printf("Aborting because of SYNC error\n");
+ break;
+ }
+ }
+
+ // SYNC - FSYNC
+
+ if (memcmp(prevsync, "\x00\x00\x00", 3) == 0) {
+ if ( (memcmp(p + 1, "\x07\x3a\xb6", 3) == 0) ||
+ (memcmp(p + 1, "\xf8\xc5\x49", 3) == 0) ) {
+ desc = "OK";
+ memcpy(prevsync, p+1, 3);
+ }
+ else {
+ desc ="Wrong FSYNC";
+ memcpy(prevsync, "\x00\x00\x00", 3);
+ }
+ } else if (memcmp(prevsync, "\x07\x3a\xb6", 3) == 0) {
+ if (memcmp(p + 1, "\xf8\xc5\x49", 3) != 0) {
+ desc = "Wrong FSYNC";
+ memcpy(prevsync, "\x00\x00\x00", 3);
+ } else {
+ desc = "OK";
+ memcpy(prevsync, p + 1, 3);
+ }
+ } else if (memcmp(prevsync, "\xf8\xc5\x49", 3) == 0) {
+ if (memcmp(p + 1, "\x07\x3a\xb6", 3) != 0) {
+ desc = "Wrong FSYNC";
+ memcpy(prevsync, "\x00\x00\x00", 3);
+ } else {
+ desc = "OK";
+ memcpy(prevsync, p + 1, 3);
+ }
+ }
+ printbuf("Sync FSYNC", 1, p + 1, 3, desc);
+
+ // LIDATA
+ printbuf("LDATA", 0, NULL, 0);
+ // LIDATA - FC
+ printbuf("FC - Frame Characterization field", 1, p+4, 4);
+ // LIDATA - FC - FCT
+ char fct_str[25];
+ sprintf(fct_str, "%d", p[4]);
+ int fct = p[4];
+ printbuf("FCT - Frame Count", 2, p+4, 1, fct_str);
+ // LIDATA - FC - FICF
+ ficf = (p[5] & 0x80) >> 7;
+
+ {
+ stringstream ss;
+ ss << (int)ficf;
+ if (ficf == 1) {
+ ss << "- FIC Information are present";
+ }
+ else {
+ ss << "- FIC Information are not present";
+ }
+
+ printbuf("FICF - Fast Information Channel Flag", 2, NULL, 0, ss.str());
+ }
+
+ // LIDATA - FC - NST
+ nst = p[5] & 0x7F;
+ {
+ stringstream ss;
+ ss << (int)nst;
+ printbuf("NST - Number of streams", 2, NULL, 0, ss.str());
+ }
+
+ // LIDATA - FC - FP
+ fp = (p[6] & 0xE0) >> 5;
+ {
+ stringstream ss;
+ ss << (int)fp;
+ printbuf("FP - Frame Phase", 2, &fp, 1, ss.str());
+ }
+
+ // LIDATA - FC - MID
+ mid = (p[6] & 0x18) >> 3;
+ {
+ stringstream ss;
+ ss << "Mode ";
+ if (mid != 0) {
+ ss << (int)mid;
+ }
+ else {
+ ss << "4";
+ }
+ printbuf("MID - Mode Identity", 2, &mid, 1, ss.str());
+ set_mode_identity(mid);
+ }
+
+ // LIDATA - FC - FL
+ fl = (p[6] & 0x07) * 256 + p[7];
+ {
+ stringstream ss;
+ ss << fl << " words";
+ printbuf("FL - Frame Length", 2, NULL, 0, ss.str());
+ }
+
+ if (ficf == 0) {
+ ficl = 0;
+ }
+ else if (mid == 3) {
+ ficl = 32;
+ }
+ else {
+ ficl = 24;
+ }
+
+ // STC
+ printbuf("STC - Stream Characterisation", 1, NULL, 0);
+
+ for (int i=0; i < nst; i++) {
+ sprintf(sdesc, "Stream number %d", i);
+ printbuf("STC - Stream Characterisation", 2, p + 8 + 4*i, 4, sdesc);
+ scid = (p[8 + 4*i] & 0xFC) >> 2;
+ sprintf(sdesc, "%d", scid);
+ printbuf("SCID - Sub-channel Identifier", 3, NULL, 0, sdesc);
+ sad[i] = (p[8+4*i] & 0x03) * 256 + p[9+4*i];
+ sprintf(sdesc, "%d", sad[i]);
+ printbuf("SAD - Sub-channel Start Address", 3, NULL, 0, sdesc);
+ tpl = (p[10+4*i] & 0xFC) >> 2;
+
+ if ((tpl & 0x20) >> 5 == 1) {
+ uint8_t opt, plevel;
+ string plevelstr;
+ opt = (tpl & 0x1c) >> 2;
+ plevel = (tpl & 0x03);
+ if (opt == 0x00) {
+ if (plevel == 0)
+ plevelstr = "1-A, 1/4, 16 CUs";
+ else if (plevel == 1)
+ plevelstr = "2-A, 3/8, 8 CUs";
+ else if (plevel == 2)
+ plevelstr = "3-A, 1/2, 6 CUs";
+ else if (plevel == 3)
+ plevelstr = "4-A, 3/4, 4 CUs";
+ }
+ else if (opt == 0x01) {
+ if (plevel == 0)
+ plevelstr = "1-B, 4/9, 27 CUs";
+ else if (plevel == 1)
+ plevelstr = "2-B, 4/7, 21 CUs";
+ else if (plevel == 2)
+ plevelstr = "3-B, 4/6, 18 CUs";
+ else if (plevel == 3)
+ plevelstr = "4-B, 4/5, 15 CUs";
+ }
+ else {
+ stringstream ss;
+ ss << "Unknown option " << opt;
+ plevelstr = ss.str();
+ }
+ sprintf(sdesc, "0x%02x - Equal Error Protection. %s", tpl, plevelstr.c_str());
+ }
+ else {
+ uint8_t tsw, uepidx;
+ tsw = (tpl & 0x08);
+ uepidx = tpl & 0x07;
+ sprintf(sdesc, "0x%02x - Unequal Error Protection. Table switch %d, UEP index %d", tpl, tsw, uepidx);
+ }
+ printbuf("TPL - Sub-channel Type and Protection Level", 3, NULL, 0, sdesc);
+ stl[i] = (p[10+4*i] & 0x03) * 256 + \
+ p[11+4*i];
+ sprintf(sdesc, "%d => %d kbit/s", stl[i], stl[i]*8/3);
+ printbuf("STL - Sub-channel Stream Length", 3, NULL, 0, sdesc);
+
+ if (config.streams_to_decode.count(i) > 0) {
+ config.streams_to_decode[i].set_subchannel_index(stl[i]/3);
+ config.streams_to_decode[i].set_index(i);
+ }
+ }
+
+ // EOH
+ printbuf("EOH - End Of Header", 1, p + 8 + 4*nst, 4);
+ uint16_t mnsc = p[8 + 4*nst] * 256 + \
+ p[8 + 4*nst + 1];
+ {
+ stringstream ss;
+ ss << mnsc;
+ printbuf("MNSC - Multiplex Network Signalling Channel", 2, p+8+4*nst, 2, ss.str());
+ }
+
+ crch = p[8 + 4*nst + 2]*256 + \
+ p[8 + 4*nst + 3];
+ crc = 0xffff;
+
+ for (int i=4; i < 8 + 4*nst + 2; i++)
+ crc = update_crc_ccitt(crc, p[i]);
+ crc =~ crc;
+
+ if (crc == crch) {
+ sprintf(sdesc,"CRC OK");
+ }
+ else {
+ sprintf(sdesc,"CRC Mismatch: %02x",crc);
+ }
+
+ printbuf("Header CRC", 2, p + 8 + 4*nst + 2, 2, sdesc);
+
+ // MST - FIC
+ if (ficf == 1) {
+ int endmarker = 0;
+ int figcount = 0;
+ uint8_t *fib, *fig;
+ uint16_t figcrc;
+
+ FIGalyser figs;
+
+ uint8_t ficdata[32*4];
+ memcpy(ficdata, p + 12 + 4*nst, ficl*4);
+ sprintf(sdesc, "FIC Data (%d bytes)", ficl*4);
+ //printbuf(sdesc, 1, ficdata, ficl*4);
+ printbuf(sdesc, 1, NULL, 0);
+ fib = p + 12 + 4*nst;
+ for(int i = 0; i < ficl*4/32; i++) {
+ sprintf(sdesc, "FIB %d", i);
+ printbuf(sdesc, 1, NULL, 0);
+ fig=fib;
+ figs.set_fib(i);
+ rate_new_fib(i);
+ endmarker=0;
+ figcount=0;
+ while (!endmarker) {
+ uint8_t figtype, figlen;
+ figtype = (fig[0] & 0xE0) >> 5;
+ if (figtype != 7) {
+ figlen = fig[0] & 0x1F;
+ sprintf(sdesc, "FIG %d [%d bytes]", figtype, figlen);
+ printbuf(sdesc, 3, fig+1, figlen);
+ decodeFIG(figs, wm_decoder, fig+1, figlen, figtype, 4);
+ fig += figlen + 1;
+ figcount += figlen + 1;
+ if (figcount >= 29)
+ endmarker = 1;
+ }
+ else {
+ endmarker = 1;
+ }
+ }
+ figcrc = fib[30]*256 + fib[31];
+ crc = 0xffff;
+ for (int j = 0; j < 30; j++) {
+ crc = update_crc_ccitt(crc, fib[j]);
+ }
+ crc =~ crc;
+ if (crc == figcrc)
+ sprintf(sdesc, "FIB %d CRC OK", i);
+ else
+ sprintf(sdesc, "FIB %d CRC Mismatch: %02x", i, crc);
+
+ printbuf(sdesc,3,fib+30,2);
+ fib += 32;
+ }
+
+ if (config.analyse_fic_carousel) {
+ figs.analyse();
+ }
+ }
+
+ int offset = 0;
+ for (int i=0; i < nst; i++) {
+ uint8_t streamdata[684*8];
+ memcpy(streamdata, p + 12 + 4*nst + ficf*ficl*4 + offset, stl[i]*8);
+ offset += stl[i] * 8;
+ if (config.streams_to_decode.count(i) > 0) {
+ sprintf(sdesc, "id %d, len %d, selected for decoding", i, stl[i]*8);
+ }
+ else {
+ sprintf(sdesc, "id %d, len %d, not selected for decoding", i, stl[i]*8);
+ }
+ if (get_verbosity() > 1) {
+ printbuf("Stream Data", 1, streamdata, stl[i]*8, sdesc);
+ }
+ else {
+ printbuf("Stream Data", 1, streamdata, 0, sdesc);
+ }
+
+ if (config.streams_to_decode.count(i) > 0) {
+ config.streams_to_decode[i].push(streamdata, stl[i]*8);
+ }
+
+ }
+
+ //* EOF (4 Bytes)
+ // CRC (2 Bytes)
+ crch = p[12 + 4*nst + ficf*ficl*4 + offset] * 256 + \
+ p[12 + 4*nst + ficf*ficl*4 + offset + 1];
+
+ crc = 0xffff;
+
+ for (int i = 12 + 4*nst; i < 12 + 4*nst + ficf*ficl*4 + offset; i++)
+ crc = update_crc_ccitt(crc, p[i]);
+ crc =~ crc;
+ if (crc == crch)
+ sprintf(sdesc, "CRC OK");
+ else
+ sprintf(sdesc, "CRC Mismatch: %02x", crc);
+
+ printbuf("EOF", 1, p + 12 + 4*nst + ficf*ficl*4 + offset, 4);
+ printbuf("CRC", 2, p + 12 + 4*nst + ficf*ficl*4 + offset, 2, sdesc);
+
+ // RFU (2 Bytes)
+ printbuf("RFU", 2, p + 12 + 4*nst + ficf*ficl*4 + offset + 2, 2);
+
+ //* TIST (4 Bytes)
+ const size_t tist_ix = 12 + 4*nst + ficf*ficl*4 + offset + 4;
+ uint32_t TIST = (uint32_t)(p[tist_ix]) << 24 |
+ (uint32_t)(p[tist_ix+1]) << 16 |
+ (uint32_t)(p[tist_ix+2]) << 8 |
+ (uint32_t)(p[tist_ix+3]);
+
+ sprintf(sdesc, "%f ms", (TIST & 0xFFFFFF) / 16384.0);
+ printbuf("TIST - Time Stamp", 1, p + tist_ix, 4, sdesc);
+
+ if (get_verbosity()) {
+ printf("-------------------------------------------------------------------------------------------------------------\n");
+ }
+
+ if (config.analyse_fig_rates and (fct % 250) == 0) {
+ rate_display_analysis(false, config.analyse_fig_rates_per_second);
+ }
+ }
+
+
+ std::map<int, DabPlusSnoop>::iterator it;
+ for (it = config.streams_to_decode.begin();
+ it != config.streams_to_decode.end();
+ ++it) {
+ it->second.close();
+ }
+
+ if (config.decode_watermark) {
+ std::string watermark(wm_decoder.calculate_watermark());
+ printf("Watermark:\n %s\n", watermark.c_str());
+ }
+
+ if (config.analyse_fig_rates) {
+ rate_display_analysis(false, config.analyse_fig_rates_per_second);
+ }
+
+ figs_cleardb();
+
+
+ return 0;
+}
+
+void decodeFIG(FIGalyser &figs,
+ WatermarkDecoder &wm_decoder,
+ uint8_t* f,
+ uint8_t figlen,
+ uint16_t figtype,
+ int indent)
+{
+ char desc[512];
+
+ switch (figtype) {
+ case 0:
+ {
+ fig0_common_t fig0(f, figlen, wm_decoder);
+
+ sprintf(desc, "FIG %d/%d: C/N=%d OE=%d P/D=%d",
+ figtype, fig0.ext(), fig0.cn(), fig0.oe(), fig0.pd());
+ printbuf(desc, indent, f+1, figlen-1);
+
+ figs.push_back(figtype, fig0.ext(), figlen);
+
+ bool complete = fig0_select(fig0, indent);
+ rate_announce_fig(figtype, fig0.ext(), complete);
+ }
+ break;
+
+ case 1:
+ {// SHORT LABELS
+ fig1_common_t fig1(f, figlen);
+
+ figs.push_back(figtype, fig1.ext(), figlen);
+
+ bool complete = fig1_select(fig1, indent);
+ rate_announce_fig(figtype, fig1.ext(), complete);
+ }
+ break;
+ case 2:
+ {// LONG LABELS
+ uint16_t ext,oe;
+
+ uint8_t toggle_flag = (f[0] & 0x80) >> 7;
+ uint8_t segment_index = (f[0] & 0x70) >> 4;
+ oe = (f[0] & 0x08) >> 3;
+ ext = f[0] & 0x07;
+ sprintf(desc,
+ "FIG %d/%d: Toggle flag=%d, Segment_index=%d, OE=%d",
+ figtype, ext, toggle_flag, segment_index, oe);
+
+ printbuf(desc, indent, f+1, figlen-1);
+
+ figs.push_back(figtype, ext, figlen);
+
+ bool complete = true; // TODO verify
+ rate_announce_fig(figtype, ext, complete);
+ }
+ break;
+ case 5:
+ {// FIDC
+ uint16_t ext;
+
+ uint8_t d1 = (f[0] & 0x80) >> 7;
+ uint8_t d2 = (f[0] & 0x40) >> 6;
+ uint8_t tcid = (f[0] & 0x38) >> 5;
+ ext = f[0] & 0x07;
+ sprintf(desc,
+ "FIG %d/%d: D1=%d, D2=%d, TCId=%d",
+ figtype, ext, d1, d2, tcid);
+
+ printbuf(desc, indent, f+1, figlen-1);
+
+ figs.push_back(figtype, ext, figlen);
+
+ bool complete = true; // TODO verify
+ rate_announce_fig(figtype, ext, complete);
+ }
+ break;
+ case 6:
+ {// Conditional access
+ fprintf(stderr, "ERROR: ETI contains unsupported FIG 6\n");
+ }
+ break;
+ default:
+ {
+ fprintf(stderr, "ERROR: ETI contains unknown FIG %d\n", figtype);
+ }
+ break;
+ }
+}
+
+
diff --git a/src/faad_decoder.cpp b/src/faad_decoder.cpp
new file mode 100644
index 0000000..73ea374
--- /dev/null
+++ b/src/faad_decoder.cpp
@@ -0,0 +1,177 @@
+/*
+ Copyright (C) 2014 Matthias P. Braendli (http://www.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/>.
+
+ faad_decoder.cpp
+ Use libfaad to decode the AAC content of the DAB+ subchannel
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+*/
+
+#include "faad_decoder.hpp"
+extern "C" {
+#include "wavfile.h"
+}
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cassert>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+
+FaadDecoder::FaadDecoder() :
+ m_data_len(0),
+ m_fd(NULL),
+ m_initialised(false)
+{
+}
+
+void FaadDecoder::open(string filename, bool ps_flag, bool aac_channel_mode,
+ bool dac_rate, bool sbr_flag, int mpeg_surround_config)
+{
+ m_filename = filename;
+ m_ps_flag = ps_flag;
+ m_aac_channel_mode = aac_channel_mode;
+ m_dac_rate = dac_rate;
+ m_sbr_flag = sbr_flag;
+ m_mpeg_surround_config = mpeg_surround_config;
+}
+
+bool FaadDecoder::decode(vector<vector<uint8_t> > aus)
+{
+ for (size_t au_ix = 0; au_ix < aus.size(); au_ix++) {
+
+ vector<uint8_t>& au = aus[au_ix];
+
+ NeAACDecFrameInfo hInfo;
+ int16_t* outBuffer;
+
+ if (!m_initialised) {
+ /* AudioSpecificConfig structure (the only way to select 960 transform here!)
+ *
+ * 00010 = AudioObjectType 2 (AAC LC)
+ * xxxx = (core) sample rate index
+ * xxxx = (core) channel config
+ * 100 = GASpecificConfig with 960 transform
+ *
+ * SBR: implicit signaling sufficient - libfaad2 automatically assumes SBR on sample rates <= 24 kHz
+ * => explicit signaling works, too, but is not necessary here
+ *
+ * PS: implicit signaling sufficient - libfaad2 therefore always uses stereo output (if PS support was enabled)
+ * => explicit signaling not possible, as libfaad2 does not support AudioObjectType 29 (PS)
+ */
+
+ int core_sr_index = m_dac_rate ? (m_sbr_flag ? 6 : 3) : (m_sbr_flag ? 8 : 5); // 24/48/16/32 kHz
+ int core_ch_config = get_aac_channel_configuration();
+ if(core_ch_config == -1) {
+ printf("Unrecognized mpeg surround config (ignored): %d\n", m_mpeg_surround_config);
+ return false;
+ }
+
+ uint8_t asc[2];
+ asc[0] = 0b00010 << 3 | core_sr_index >> 1;
+ asc[1] = (core_sr_index & 0x01) << 7 | core_ch_config << 3 | 0b100;
+
+
+ long unsigned samplerate;
+ unsigned char channels;
+
+ long int init_result = NeAACDecInit2(m_faad_handle.decoder, asc, sizeof(asc), &samplerate, &channels);
+ if(init_result != 0) {
+ /* If some error initializing occured, skip the file */
+ printf("Error initializing decoder library: %s\n", NeAACDecGetErrorMessage(-init_result));
+ NeAACDecClose(m_faad_handle.decoder);
+ return false;
+ }
+
+ m_initialised = true;
+ }
+
+ outBuffer = (int16_t *)NeAACDecDecode(m_faad_handle.decoder, &hInfo, &au[0], au.size());
+ assert(outBuffer != NULL);
+
+ m_sample_rate = hInfo.samplerate;
+ m_channels = hInfo.channels;
+ size_t samples = hInfo.samples;
+
+#if 0
+ printf("bytes consumed %d\n", (int)(hInfo.bytesconsumed));
+ printf("samplerate = %d, samples = %zu, channels = %d,"
+ " error = %d, sbr = %d\n", m_sample_rate, samples,
+ m_channels, hInfo.error, hInfo.sbr);
+ printf("header = %d\n", hInfo.header_type);
+#endif
+
+ if (hInfo.error != 0) {
+ printf("FAAD Warning: %s\n",
+ faacDecGetErrorMessage(hInfo.error));
+ return false;
+ }
+
+ if (m_fd == NULL) {
+ stringstream ss;
+ ss << m_filename << ".wav";
+ m_fd = wavfile_open(ss.str().c_str(), m_sample_rate);
+ }
+
+ if (samples) {
+ if (m_channels == 1) {
+ int16_t *buffer = (int16_t *)alloca (2 * samples);
+ size_t i;
+ for (i = 0; i < samples; i ++) {
+ buffer [2 * i] = ((int16_t *)outBuffer) [i];
+ buffer [2 * i + 1] = buffer [2 * i];
+ }
+ wavfile_write(m_fd, buffer, 2*samples);
+ }
+ else if (m_channels == 2) {
+ wavfile_write(m_fd, outBuffer, samples);
+ }
+ else {
+ printf("Cannot handle %d channels\n", m_channels);
+ }
+ }
+
+ }
+ return true;
+}
+
+void FaadDecoder::close()
+{
+ if (m_initialised) {
+ wavfile_close(m_fd);
+ }
+}
+
+int FaadDecoder::get_aac_channel_configuration()
+{
+ switch(m_mpeg_surround_config) {
+ case 0: // no surround
+ return m_aac_channel_mode ? 2 : 1;
+ case 1: // 5.1
+ return 6;
+ case 2: // 7.1
+ return 7;
+ default:
+ return -1;
+ }
+}
+
+
diff --git a/src/faad_decoder.hpp b/src/faad_decoder.hpp
new file mode 100644
index 0000000..470f905
--- /dev/null
+++ b/src/faad_decoder.hpp
@@ -0,0 +1,100 @@
+/*
+ Copyright (C) 2014 Matthias P. Braendli (http://www.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/>.
+
+ faad_decoder.h
+ Use libfaad to decode the AAC content of the DAB+ subchannel
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <neaacdec.h>
+
+#ifndef __FAAD_DECODER_H_
+#define __FAAD_DECODER_H_
+
+class FaadHandle
+{
+ public:
+ FaadHandle()
+ {
+ decoder = NeAACDecOpen();
+ }
+
+ FaadHandle(const FaadHandle&)
+ {
+ this->decoder = NeAACDecOpen();
+ }
+
+ FaadHandle& operator=(const FaadHandle&)
+ {
+ this->decoder = NeAACDecOpen();
+ return *this;
+ }
+
+ ~FaadHandle()
+ {
+ NeAACDecClose(decoder);
+ decoder = NULL;
+ }
+
+ NeAACDecHandle decoder;
+};
+
+class FaadDecoder
+{
+ public:
+ FaadDecoder();
+
+ void open(std::string filename, bool ps_flag, bool aac_channel_mode,
+ bool dac_rate, bool sbr_flag, int mpeg_surround_config);
+
+ void close(void);
+
+ bool decode(std::vector<std::vector<uint8_t> > aus);
+
+ bool is_initialised(void) { return m_initialised; }
+
+ private:
+ int get_aac_channel_configuration();
+ size_t m_data_len;
+
+ std::string m_filename;
+ FILE* m_fd;
+
+ /* Data needed for FAAD */
+ bool m_ps_flag;
+ bool m_aac_channel_mode;
+ bool m_dac_rate;
+ bool m_sbr_flag;
+ int m_mpeg_surround_config;
+
+ int m_channels;
+ int m_sample_rate;
+
+ bool m_initialised;
+ FaadHandle m_faad_handle;
+};
+
+#endif
+
diff --git a/src/fig0_0.cpp b/src/fig0_0.cpp
new file mode 100644
index 0000000..82bfadf
--- /dev/null
+++ b/src/fig0_0.cpp
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+
+// FIG 0/0 Ensemble information
+// ETSI EN 300 401 6.4
+bool fig0_0(fig0_common_t& fig0, int indent)
+{
+ uint8_t cid, al, ch, hic, lowc, occ;
+ uint16_t eid, eref;
+ char desc[128];
+ uint8_t* f = fig0.f;
+
+ eid = f[1]*256+f[2];
+ cid = (f[1] & 0xF0) >> 4;
+ eref = (f[1] & 0x0F)*256 + \
+ f[2];
+ ch = (f[3] & 0xC0) >> 6;
+ al = (f[3] & 0x20) >> 5;
+ hic = f[3] & 0x1F;
+ lowc = f[4];
+ if (ch != 0) {
+ occ = f[5];
+ sprintf(desc,
+ "Ensemble ID=0x%02x (Country id=%d, Ensemble reference=%d), Change flag=%d, Alarm flag=%d, CIF Count=%d/%d, Occurance change=%d",
+ eid, cid, eref, ch, al, hic, lowc, occ);
+ }
+ else {
+ sprintf(desc,
+ "Ensemble ID=0x%02x (Country id=%d, Ensemble reference=%d), Change flag=%d, Alarm flag=%d, CIF Count=%d/%d",
+ eid, cid, eref, ch, al, hic, lowc);
+ }
+ printbuf(desc, indent+1, NULL, 0);
+
+ return true;
+}
+
diff --git a/src/fig0_1.cpp b/src/fig0_1.cpp
new file mode 100644
index 0000000..f5df4c4
--- /dev/null
+++ b/src/fig0_1.cpp
@@ -0,0 +1,112 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <unordered_set>
+
+static std::unordered_set<int> subchannels_seen;
+
+bool fig0_1_is_complete(int subch_id)
+{
+ bool complete = subchannels_seen.count(subch_id);
+
+ if (complete) {
+ subchannels_seen.clear();
+ }
+ else {
+ subchannels_seen.insert(subch_id);
+ }
+
+ return complete;
+}
+
+// FIG 0/1 Basic sub-channel organization
+// ETSI EN 300 401 6.2.1
+bool fig0_1(fig0_common_t& fig0, int indent)
+{
+ int i = 1;
+ uint8_t* f = fig0.f;
+ char desc[128];
+ bool complete = false;
+
+ while (i < fig0.figlen-3) {
+ // iterate over subchannels
+ int subch_id = f[i] >> 2;
+ complete |= fig0_1_is_complete(subch_id);
+
+ int start_addr = ((f[i] & 0x03) << 8) |
+ (f[i+1]);
+ int long_flag = (f[i+2] >> 7);
+
+ if (long_flag) {
+ int option = (f[i+2] >> 4) & 0x07;
+ int protection_level = (f[i+2] >> 2) & 0x03;
+ int subchannel_size = ((f[i+2] & 0x03) << 8 ) |
+ f[i+3];
+
+ i += 4;
+
+ if (option == 0x00) {
+ sprintf(desc,
+ "Subch 0x%x, start_addr %d, long, EEP %d-A, subch size %d",
+ subch_id, start_addr, protection_level, subchannel_size);
+ }
+ else if (option == 0x01) {
+ sprintf(desc,
+ "Subch 0x%x, start_addr %d, long, EEP %d-B, subch size %d",
+ subch_id, start_addr, protection_level, subchannel_size);
+ }
+ else {
+ sprintf(desc,
+ "Subch 0x%x, start_addr %d, long, invalid option %d, protection %d, subch size %d",
+ subch_id, start_addr, option, protection_level, subchannel_size);
+ }
+ }
+ else {
+ int table_switch = (f[i+2] >> 6) & 0x01;
+ uint32_t table_index = (f[i+2] & 0x3F);
+
+
+ if (table_switch == 0) {
+ sprintf(desc,
+ "Subch 0x%x, start_addr %d, short, table index %d",
+ subch_id, start_addr, table_index);
+ }
+ else {
+ sprintf(desc,
+ "Subch 0x%x, start_addr %d, short, invalid table_switch(=1), table index %d",
+ subch_id, start_addr, table_index);
+ }
+
+ i += 3;
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_10.cpp b/src/fig0_10.cpp
new file mode 100644
index 0000000..b02edfa
--- /dev/null
+++ b/src/fig0_10.cpp
@@ -0,0 +1,74 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+
+// FIG 0/10 Date and time
+// ETSI EN 300 401 8.1.3.1
+bool fig0_10(fig0_common_t& fig0, int indent)
+{
+ char desc[256];
+ char dateStr[256];
+ dateStr[0] = 0;
+ uint8_t* f = fig0.f;
+
+ //bool RFU = f[1] >> 7;
+
+ uint32_t MJD = (((uint32_t)f[1] & 0x7F) << 10) |
+ ((uint32_t)(f[2]) << 2) |
+ (f[3] >> 6);
+ sprintfMJD(dateStr, MJD);
+
+ bool LSI = f[3] & 0x20;
+ bool ConfInd = f[3] & 0x10;
+ fig0.wm_decoder.push_confind_bit(ConfInd);
+ bool UTC = f[3] & 0x8;
+
+ uint8_t hours = ((f[3] & 0x7) << 2) |
+ ( f[4] >> 6);
+
+ uint8_t minutes = f[4] & 0x3f;
+
+ if (UTC) {
+ uint8_t seconds = f[5] >> 2;
+ uint16_t milliseconds = ((uint16_t)(f[5] & 0x3) << 8) | f[6];
+
+ sprintf(desc, "FIG 0/%d(long): MJD=0x%X %s, LSI %u, ConfInd %u, UTC Time: %02d:%02d:%02d.%d",
+ fig0.ext(), MJD, dateStr, LSI, ConfInd, hours, minutes, seconds, milliseconds);
+ printbuf(desc, indent+1, NULL, 0);
+ }
+ else {
+ sprintf(desc, "FIG 0/%d(short): MJD=0x%X %s, LSI %u, ConfInd %u, UTC Time: %02d:%02d",
+ fig0.ext(), MJD, dateStr, LSI, ConfInd, hours, minutes);
+ printbuf(desc, indent+1, NULL, 0);
+ }
+
+ return true;
+}
+
diff --git a/src/fig0_11.cpp b/src/fig0_11.cpp
new file mode 100644
index 0000000..40136c3
--- /dev/null
+++ b/src/fig0_11.cpp
@@ -0,0 +1,209 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> region_ids_seen;
+
+bool fig0_11_is_complete(int region_id)
+{
+ bool complete = region_ids_seen.count(region_id);
+
+ if (complete) {
+ region_ids_seen.clear();
+ }
+ else {
+ region_ids_seen.insert(region_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/11 Region definition
+// ETSI EN 300 401 8.1.16.1
+bool fig0_11(fig0_common_t& fig0, int indent)
+{
+ Lat_Lng gps_pos = {0, 0};
+ int16_t Latitude_coarse, Longitude_coarse;
+ uint16_t Region_Id, Extent_Latitude, Extent_Longitude, key;
+ uint8_t i = 1, j, k, GATy, Rfu, Length_TII_list, Rfa, MainId, Length_SubId_list, SubId;
+ int8_t bit_pos;
+ char desc[256];
+ char tmpbuf[256];
+ bool GE_flag;
+ uint8_t* f = fig0.f;
+ uint8_t Mode_Identity = get_mode_identity();
+ bool complete = false;
+
+ while (i < (fig0.figlen - 1)) {
+ // iterate over Region definition
+ GATy = f[i] >> 4;
+ GE_flag = (f[i] >> 3) & 0x01;
+ Region_Id = ((uint16_t)(f[i] & 0x07) << 8) | ((uint16_t)f[i+1]);
+ complete |= fig0_11_is_complete(Region_Id);
+
+ key = ((uint16_t)fig0.oe() << 12) | ((uint16_t)fig0.pd() << 11) | Region_Id;
+ i += 2;
+ if (GATy == 0) {
+ // TII list
+ sprintf(desc, "GATy=%d Geographical area defined by a TII list, G/E flag=%d %s coverage area, RegionId=0x%X, database key=0x%X",
+ GATy, GE_flag, GE_flag?"Global":"Ensemble", Region_Id, key);
+ if (i < fig0.figlen) {
+ Rfu = f[i] >> 5;
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ Length_TII_list = f[i] & 0x1F;
+ sprintf(tmpbuf, ", Length of TII list=%d", Length_TII_list);
+ strcat(desc, tmpbuf);
+ if (Length_TII_list == 0) {
+ strcat(desc, ", CEI");
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ i++;
+
+ for(j = 0;(i < (fig0.figlen - 1)) && (j < Length_TII_list); j++) {
+ // iterate over Transmitter group
+ Rfa = f[i] >> 7;
+ MainId = f[i] & 0x7F;
+ if (Rfa != 0) {
+ sprintf(desc, "Rfa=%d invalid value, MainId=0x%X",
+ Rfa, MainId);
+ }
+ else {
+ sprintf(desc, "MainId=0x%X", MainId);
+ }
+ // check MainId value
+ if ((Mode_Identity == 1) || (Mode_Identity == 2) || (Mode_Identity == 4)) {
+ if (MainId > 69) {
+ // The coding range shall be 0 to 69 for transmission modes I, II and IV
+ sprintf(tmpbuf, " invalid value for transmission mode %d", Mode_Identity);
+ strcat(desc, tmpbuf);
+ }
+ }
+ else if (Mode_Identity == 3) {
+ if (MainId > 5) {
+ // The coding range shall be 0 to 5 for transmission modes I, II and IV
+ sprintf(tmpbuf, " invalid value for transmission mode %d", Mode_Identity);
+ strcat(desc, tmpbuf);
+ }
+ }
+ Rfa = f[i+1] >> 5;
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ Length_SubId_list = f[i+1] & 0x1F;
+ sprintf(tmpbuf, ", Length of SubId=%d", Length_SubId_list);
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+2, NULL, 0);
+ i += 2;
+
+ bit_pos = 3;
+ SubId = 0;
+ for(k = 0;(i < fig0.figlen) && (k < Length_SubId_list); k++) {
+ // iterate SubId
+ if (bit_pos >= 0) {
+ SubId |= (f[i] >> bit_pos) & 0x1F;
+ sprintf(desc, "SubId=0x%X", SubId);
+ // check SubId value
+ if ((SubId == 0) || (SubId > 23)) {
+ strcat(desc, " invalid value");
+ }
+ printbuf(desc, indent+3, NULL, 0);
+ bit_pos -= 5;
+ SubId = 0;
+ }
+ if (bit_pos < 0) {
+ SubId = (f[i] << abs(bit_pos)) & 0x1F;
+ bit_pos += 8;
+ i++;
+ }
+ }
+ if (bit_pos > 3) {
+ // jump padding
+ i++;
+ }
+ if (k < Length_SubId_list) {
+ sprintf(desc, "%d SubId missing, fig length too short !", (Length_SubId_list - k));
+ printbuf(desc, indent+3, NULL, 0);
+ fprintf(stderr, "WARNING: FIG 0/%d length %d too short !\n", fig0.ext(), fig0.figlen);
+ }
+ }
+ if (j < Length_TII_list) {
+ sprintf(desc, "%d Transmitter group missing, fig length too short !", (Length_TII_list - j));
+ printbuf(desc, indent+2, NULL, 0);
+ fprintf(stderr, "WARNING: FIG 0/%d length %d too short !\n", fig0.ext(), fig0.figlen);
+ }
+ }
+ }
+ else if (GATy == 1) {
+ // Coordinates
+ sprintf(desc, "GATy=%d Geographical area defined as a spherical rectangle by the geographical co-ordinates of one corner and its latitude and longitude extents, G/E flag=%d %s coverage area, RegionId=0x%X, database key=0x%X",
+ GATy, GE_flag, GE_flag?"Global":"Ensemble", Region_Id, key);
+ if (i < (fig0.figlen - 6)) {
+ Latitude_coarse = ((int16_t)f[i] << 8) | ((uint16_t)f[i+1]);
+ Longitude_coarse = ((int16_t)f[i+2] << 8) | ((uint16_t)f[i+3]);
+ gps_pos.latitude = ((double)Latitude_coarse) * 90 / 32768;
+ gps_pos.longitude = ((double)Latitude_coarse) * 180 / 32768;
+ sprintf(tmpbuf, ", Lat Lng coarse=0x%X 0x%X => %f, %f",
+ Latitude_coarse, Longitude_coarse, gps_pos.latitude, gps_pos.longitude);
+ strcat(desc, tmpbuf);
+ Extent_Latitude = ((uint16_t)f[i+4] << 4) | ((uint16_t)(f[i+5] >> 4));
+ Extent_Longitude = ((uint16_t)(f[i+5] & 0x0F) << 8) | ((uint16_t)f[i+6]);
+ gps_pos.latitude += ((double)Extent_Latitude) * 90 / 32768;
+ gps_pos.longitude += ((double)Extent_Longitude) * 180 / 32768;
+ sprintf(tmpbuf, ", Extent Lat Lng=0x%X 0x%X => %f, %f",
+ Extent_Latitude, Extent_Longitude, gps_pos.latitude, gps_pos.longitude);
+ strcat(desc, tmpbuf);
+ }
+ else {
+ sprintf(tmpbuf, ", Coordinates missing, fig length too short !");
+ strcat(desc, tmpbuf);
+ fprintf(stderr, "WARNING: FIG 0/%d length %d too short !\n", fig0.ext(), fig0.figlen);
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ i += 7;
+ }
+ else {
+ // Rfu
+ sprintf(desc, "GATy=%d reserved for future use of the geographical, G/E flag=%d %s coverage area, RegionId=0x%X, database key=0x%X, stop Region definition iteration %d/%d",
+ GATy, GE_flag, GE_flag?"Global":"Ensemble", Region_Id, key, i, fig0.figlen);
+ printbuf(desc, indent+1, NULL, 0);
+ // stop Region definition iteration
+ i = fig0.figlen;
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_13.cpp b/src/fig0_13.cpp
new file mode 100644
index 0000000..ffb5b89
--- /dev/null
+++ b/src/fig0_13.cpp
@@ -0,0 +1,131 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <set>
+
+/* EN 300 401, 8.1.20, User application information
+ * The combination of the SId and the SCIdS provides a service component
+ * identifier which is valid globally.
+ */
+using SId_t = int;
+using SCIdS_t = int;
+static std::set<std::pair<SId_t, SCIdS_t> > components_ids_seen;
+
+bool fig0_13_is_complete(SId_t SId, SCIdS_t SCIdS)
+{
+ auto key = std::make_pair(SId, SCIdS);
+ bool complete = components_ids_seen.count(key);
+
+ if (complete) {
+ components_ids_seen.clear();
+ }
+ else {
+ components_ids_seen.insert(key);
+ }
+
+ return complete;
+}
+
+
+std::string get_fig_0_13_userapp(int user_app_type)
+{
+ switch (user_app_type) {
+ case 0x000: return "Reserved for future definition";
+ case 0x001: return "Not used";
+ case 0x002: return "MOT Slideshow";
+ case 0x003: return "MOT Broadacst Web Site";
+ case 0x004: return "TPEG";
+ case 0x005: return "DGPS";
+ case 0x006: return "TMC";
+ case 0x007: return "EPG";
+ case 0x008: return "DAB Java";
+ case 0x44a: return "Journaline";
+ default: return "Reserved for future applications";
+ }
+}
+
+// FIG 0/13 User application information
+// ETSI EN 300 401 8.1.20
+bool fig0_13(fig0_common_t& fig0, int indent)
+{
+ uint32_t SId;
+ uint8_t SCIdS;
+ uint8_t No;
+ uint8_t* f = fig0.f;
+ const int figtype = 0;
+ char desc[256];
+ bool complete = false;
+
+ int k = 1;
+
+ if (fig0.pd() == 0) { // Programme services, 16 bit SId
+ SId = (f[k] << 8) |
+ f[k+1];
+ k+=2;
+
+ SCIdS = f[k] >> 4;
+ No = f[k] & 0x0F;
+ k++;
+ }
+ else { // Data services, 32 bit SId
+ SId = (f[k] << 24) |
+ (f[k+1] << 16) |
+ (f[k+2] << 8) |
+ f[k+3];
+ k+=4;
+
+ SCIdS = f[k] >> 4;
+ No = f[k] & 0x0F;
+ k++;
+
+ }
+
+ complete |= fig0_13_is_complete(SId, SCIdS);
+
+ sprintf(desc, "FIG %d/%d: SId=0x%X SCIdS=%u No=%u",
+ figtype, fig0.ext(), SId, SCIdS, No);
+ printbuf(desc, indent+1, NULL, 0);
+
+ for (int numapp = 0; numapp < No; numapp++) {
+ uint16_t user_app_type = ((f[k] << 8) |
+ (f[k+1] & 0xE0)) >> 5;
+ uint8_t user_app_len = f[k+1] & 0x1F;
+ k+=2;
+
+ sprintf(desc, "User Application %d '%s'; length %u",
+ user_app_type,
+ get_fig_0_13_userapp(user_app_type).c_str(),
+ user_app_len);
+ printbuf(desc, indent+2, NULL, 0);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_14.cpp b/src/fig0_14.cpp
new file mode 100644
index 0000000..e72d121
--- /dev/null
+++ b/src/fig0_14.cpp
@@ -0,0 +1,81 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> subch_ids_seen;
+
+bool fig0_14_is_complete(int subch_id)
+{
+ bool complete = subch_ids_seen.count(subch_id);
+
+ if (complete) {
+ subch_ids_seen.clear();
+ }
+ else {
+ subch_ids_seen.insert(subch_id);
+ }
+
+ return complete;
+}
+
+
+// fig 0/14 FEC Scheme: this 2-bit field shall indicate the Forward Error Correction scheme in use, as follows:
+const char *FEC_schemes_str[4] = {
+ "no FEC scheme applied",
+ "FEC scheme applied according to ETSI EN 300 401 clause 5.3.5",
+ "reserved for future definition",
+ "reserved for future definition"
+};
+
+
+// FIG 0/14 FEC sub-channel organization
+// ETSI EN 300 401 6.2.2
+bool fig0_14(fig0_common_t& fig0, int indent)
+{
+ uint8_t i = 1, SubChId, FEC_scheme;
+ uint8_t* f = fig0.f;
+ char desc[256];
+ bool complete = false;
+
+ while (i < fig0.figlen) {
+ // iterate over Sub-channel
+ SubChId = f[i] >> 2;
+ complete |= fig0_14_is_complete(SubChId);
+ FEC_scheme = f[i] & 0x3;
+ sprintf(desc, "SubChId=0x%X, FEC scheme=%d %s",
+ SubChId, FEC_scheme, FEC_schemes_str[FEC_scheme]);
+ printbuf(desc, indent+1, NULL, 0);
+ i++;
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_16.cpp b/src/fig0_16.cpp
new file mode 100644
index 0000000..24c9040
--- /dev/null
+++ b/src/fig0_16.cpp
@@ -0,0 +1,127 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <set>
+
+/* SId and PNum look like good candidates to uniquely identify a FIG0_16
+ */
+using SId_t = int;
+using PNum_t = int;
+static std::set<std::pair<SId_t, PNum_t> > components_seen;
+
+bool fig0_16_is_complete(SId_t SId, PNum_t PNum)
+{
+ auto key = std::make_pair(SId, PNum);
+ bool complete = components_seen.count(key);
+
+ if (complete) {
+ components_seen.clear();
+ }
+ else {
+ components_seen.insert(key);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/16 Programme Number & fig0.oe() Programme Number
+// ETSI EN 300 401 8.1.4 & 8.1.10.3
+bool fig0_16(fig0_common_t& fig0, int indent)
+{
+ uint16_t SId, PNum, New_SId, New_PNum;
+ uint8_t i = 1, Rfa, Rfu;
+ char tmpbuf[256];
+ char desc[256];
+ bool Continuation_flag, Update_flag;
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 4)) {
+ // iterate over Programme Number
+ SId = ((uint16_t)f[i] << 8) | ((uint16_t)f[i+1]);
+ PNum = ((uint16_t)f[i+2] << 8) | ((uint16_t)f[i+3]);
+ complete |= fig0_16_is_complete(SId, PNum);
+ Rfa = f[i+4] >> 6;
+ Rfu = (f[i+4] >> 2) & 0x0F;
+ Continuation_flag = (f[i+4] >> 1) & 0x01;
+ Update_flag = f[i+4] & 0x01;
+
+ sprintf(desc, "SId=0x%X, PNum=0x%X ", SId, PNum);
+ // Append PNum decoded string
+ strcatPNum(desc, PNum);
+
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=0x%X invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+
+ sprintf(tmpbuf, ", Continuation flag=%d the programme will %s, Update flag=%d %sre-direction",
+ Continuation_flag, Continuation_flag?"be interrupted but continued later":"not be subject to a planned interruption",
+ Update_flag, Update_flag?"":"no ");
+ strcat(desc, tmpbuf);
+ i += 5;
+
+ if (Update_flag != 0) {
+ // In the case of a re-direction, the New SId and New PNum shall be appended
+ if (i < (fig0.figlen - 1)) {
+ New_SId = ((uint16_t)f[i] << 8) | ((uint16_t)f[i+1]);
+ sprintf(tmpbuf, ", New SId=0x%X", New_SId);
+ strcat(desc, tmpbuf);
+ if (i < (fig0.figlen - 3)) {
+ New_PNum = ((uint16_t)f[i+2] << 8) | ((uint16_t)f[i+3]);
+ sprintf(tmpbuf, ", New PNum=0x%X ", New_PNum);
+ strcat(desc, tmpbuf);
+ // Append New_PNum decoded string
+ strcatPNum(desc, New_PNum);
+ }
+ else {
+ sprintf(tmpbuf, ", missing New PNum !");
+ strcat(desc, tmpbuf);
+ }
+ }
+ else {
+ sprintf(tmpbuf, ", missing New SId and New PNum !");
+ strcat(desc, tmpbuf);
+ }
+ i += 4;
+ }
+
+ printbuf(desc, indent+1, NULL, 0);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_17.cpp b/src/fig0_17.cpp
new file mode 100644
index 0000000..01fa954
--- /dev/null
+++ b/src/fig0_17.cpp
@@ -0,0 +1,138 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> services_ids_seen;
+
+bool fig0_17_is_complete(int services_id)
+{
+ bool complete = services_ids_seen.count(services_id);
+
+ if (complete) {
+ services_ids_seen.clear();
+ }
+ else {
+ services_ids_seen.insert(services_id);
+ }
+
+ return complete;
+}
+
+// FIG 0/17 Programme Type
+// ETSI EN 300 401 8.1.5
+bool fig0_17(fig0_common_t& fig0, int indent)
+{
+ uint16_t SId;
+ uint8_t i = 1, Rfa, Language, Int_code, Comp_code;
+ char tmpbuf[512];
+ char desc[512];
+ bool SD_flag, PS_flag, L_flag, CC_flag, Rfu;
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 3)) {
+ // iterate over announcement support
+ SId = (f[i] << 8) | f[i+1];
+ complete |= fig0_17_is_complete(SId);
+ SD_flag = (f[i+2] >> 7);
+ PS_flag = ((f[i+2] >> 6) & 0x01);
+ L_flag = ((f[i+2] >> 5) & 0x01);
+ CC_flag = ((f[i+2] >> 4) & 0x01);
+ Rfa = (f[i+2] & 0x0F);
+ sprintf(desc, "SId=0x%X, S/D=%d Programme Type codes and language (when present), %srepresent the current programme contents, P/S=%d %s service component, L flag=%d language field %s, CC flag=%d complementary code and preceding Rfa and Rfu fields %s",
+ SId, SD_flag, SD_flag?"":"may not ", PS_flag, PS_flag?"secondary":"primary",
+ L_flag, L_flag?"present":"absent", CC_flag, CC_flag?"present":"absent");
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=0x%X invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ i += 3;
+ if (L_flag != 0) {
+ if (i < fig0.figlen) {
+ Language = f[i];
+ sprintf(tmpbuf, ", Language=0x%X %s", Language,
+ get_language_name(Language));
+ strcat(desc, tmpbuf);
+ }
+ else {
+ sprintf(tmpbuf, ", Language= invalid FIG length");
+ strcat(desc, tmpbuf);
+ }
+ i++;
+ }
+ if (i < fig0.figlen) {
+ Rfa = f[i] >> 6;
+ Rfu = (f[i] >> 5) & 0x01;
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=0x%X invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ Int_code = f[i] & 0x1F;
+ sprintf(tmpbuf, ", Int code=0x%X %s", Int_code, get_programme_type(get_international_table() , Int_code));
+ strcat(desc, tmpbuf);
+ i++;
+ }
+ else {
+ sprintf(tmpbuf, ", Int code= invalid FIG length");
+ strcat(desc, tmpbuf);
+ }
+ if (CC_flag != 0) {
+ if (i < fig0.figlen) {
+ Rfa = f[i] >> 6;
+ Rfu = (f[i] >> 5) & 0x01;
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=0x%X invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ Comp_code = f[i] & 0x1F;
+ sprintf(tmpbuf, ", Comp code=0x%X %s", Comp_code, get_programme_type(get_international_table() , Comp_code));
+ strcat(desc, tmpbuf);
+ i++;
+ }
+ else {
+ sprintf(tmpbuf, ", Comp code= invalid FIG length");
+ strcat(desc, tmpbuf);
+ }
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_18.cpp b/src/fig0_18.cpp
new file mode 100644
index 0000000..46a399c
--- /dev/null
+++ b/src/fig0_18.cpp
@@ -0,0 +1,112 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> services_seen;
+
+bool fig0_18_is_complete(int services_id)
+{
+ bool complete = services_seen.count(services_id);
+
+ if (complete) {
+ services_seen.clear();
+ }
+ else {
+ services_seen.insert(services_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/18 Announcement support
+// ETSI EN 300 401 8.1.6.1
+bool fig0_18(fig0_common_t& fig0, int indent)
+{
+ uint32_t key;
+ uint16_t SId, Asu_flags;
+ uint8_t i = 1, j, Rfa, Number_clusters;
+ char tmpbuf[256];
+ char desc[256];
+ uint8_t* f = fig0.f;
+ const int figtype = 0;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 4)) {
+ // iterate over announcement support
+ // SId, Asu flags, Rfa, Number of clusters
+ SId = ((uint16_t)f[i] << 8) | (uint16_t)f[i+1];
+ complete |= fig0_18_is_complete(SId);
+ Asu_flags = ((uint16_t)f[i+2] << 8) | (uint16_t)f[i+3];
+ Rfa = (f[i+4] >> 5);
+ Number_clusters = (f[i+4] & 0x1F);
+ sprintf(desc, "SId=0x%X, Asu flags=0x%04x", SId, Asu_flags);
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Number of clusters=%d", Number_clusters);
+ strcat(desc, tmpbuf);
+ key = ((uint32_t)fig0.oe() << 17) | ((uint32_t)fig0.pd() << 16) | (uint32_t)SId;
+ sprintf(tmpbuf, ", database key=0x%05x", key);
+ strcat(desc, tmpbuf);
+ // CEI Change Event Indication
+ if ((Number_clusters == 0) && (Asu_flags == 0)) {
+ sprintf(tmpbuf, ", CEI");
+ strcat(desc, tmpbuf);
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ i += 5;
+
+ for(j = 0; (j < Number_clusters) && (i < fig0.figlen); j++) {
+ // iterate over Cluster Id
+ sprintf(desc, "Cluster Id=0x%X", f[i]);
+ printbuf(desc, indent+2, NULL, 0);
+ i++;
+ }
+ if (j < Number_clusters) {
+ sprintf(desc, "missing Cluster Id, fig length too short !");
+ printbuf(desc, indent+1, NULL, 0);
+ fprintf(stderr, "WARNING: FIG %d/%d length %d too short !\n", figtype, fig0.ext(), fig0.figlen);
+ }
+
+ // decode announcement support types
+ for(j = 0; j < 16; j++) {
+ if (Asu_flags & (1 << j)) {
+ sprintf(desc, "Announcement support=%s", get_announcement_type(j));
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_19.cpp b/src/fig0_19.cpp
new file mode 100644
index 0000000..31b4ca1
--- /dev/null
+++ b/src/fig0_19.cpp
@@ -0,0 +1,105 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> clusters_seen;
+
+bool fig0_19_is_complete(int clusters_id)
+{
+ bool complete = clusters_seen.count(clusters_id);
+
+ if (complete) {
+ clusters_seen.clear();
+ }
+ else {
+ clusters_seen.insert(clusters_id);
+ }
+
+ return complete;
+}
+
+// FIG 0/19 Announcement switching
+// ETSI EN 300 401 8.1.6.2
+bool fig0_19(fig0_common_t& fig0, int indent)
+{
+ uint16_t Asw_flags;
+ uint8_t i = 1, j, Cluster_Id, SubChId, Rfa, RegionId_LP;
+ char tmpbuf[256];
+ char desc[256];
+ bool New_flag, Region_flag;
+ uint8_t* f = fig0.f;
+ const int figtype = 0;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 3)) {
+ // iterate over announcement switching
+ // Cluster Id, Asw flags, New flag, Region flag,
+ // SubChId, Rfa, Region Id Lower Part
+ Cluster_Id = f[i];
+ complete |= fig0_19_is_complete(Cluster_Id);
+ Asw_flags = ((uint16_t)f[i+1] << 8) | (uint16_t)f[i+2];
+ New_flag = (f[i+3] >> 7);
+ Region_flag = (f[i+3] >> 6) & 0x1;
+ SubChId = (f[i+3] & 0x3F);
+ sprintf(desc, "Cluster Id=0x%02x, Asw flags=0x%04x, New flag=%d %s, Region flag=%d last byte %s, SubChId=%d",
+ Cluster_Id, Asw_flags, New_flag, (New_flag)?"new":"repeat", Region_flag, (Region_flag)?"present":"absent", SubChId);
+ if (Region_flag) {
+ if (i < (fig0.figlen - 4)) {
+ // read region lower part
+ Rfa = (f[i+4] >> 6);
+ RegionId_LP = (f[i+4] & 0x3F);
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Region Lower Part=0x%02x", RegionId_LP);
+ strcat(desc, tmpbuf);
+ }
+ else {
+ sprintf(tmpbuf, "missing Region Lower Part, fig length too short !");
+ strcat(desc, tmpbuf);
+ fprintf(stderr, "WARNING: FIG %d/%d length %d too short !\n", figtype, fig0.ext(), fig0.figlen);
+ }
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ // decode announcement switching types
+ for(j = 0; j < 16; j++) {
+ if (Asw_flags & (1 << j)) {
+ sprintf(desc, "Announcement switching=%s", get_announcement_type(j));
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+ i += (4 + Region_flag);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_2.cpp b/src/fig0_2.cpp
new file mode 100644
index 0000000..27f01ac
--- /dev/null
+++ b/src/fig0_2.cpp
@@ -0,0 +1,168 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <string>
+#include <cstring>
+#include <unordered_set>
+
+static std::unordered_set<int> services_seen;
+
+bool fig0_2_is_complete(int services_id)
+{
+ bool complete = services_seen.count(services_id);
+
+ if (complete) {
+ services_seen.clear();
+ }
+ else {
+ services_seen.insert(services_id);
+ }
+
+ return complete;
+}
+
+// FIG 0/2 Basic service and service component definition
+// ETSI EN 300 401 6.3.1
+bool fig0_2(fig0_common_t& fig0, int indent)
+{
+ uint16_t sref, sid;
+ uint8_t cid, ecc, local, caid, ncomp, timd, ps, ca, subchid, scty;
+ int k = 1;
+ std::string psdesc;
+ uint8_t* f = fig0.f;
+ char sctydesc[32];
+ char desc[256];
+ bool complete = false;
+
+ while (k < fig0.figlen) {
+ if (fig0.pd() == 0) {
+ sid = f[k] * 256 + f[k+1];
+ ecc = 0;
+ cid = (f[k] & 0xF0) >> 4;
+ sref = (f[k] & 0x0F) * 256 + f[k+1];
+ k += 2;
+ }
+ else {
+ sid = f[k] * 256 * 256 * 256 + \
+ f[k+1] * 256 * 256 + \
+ f[k+2] * 256 + \
+ f[k+3];
+
+ ecc = f[k];
+ cid = (f[k+1] & 0xF0) >> 4;
+ sref = (f[k+1] & 0x0F) * 256 * 256 + \
+ f[k+2] * 256 + \
+ f[k+3];
+
+ k += 4;
+ }
+
+ complete |= fig0_2_is_complete(sid);
+
+ local = (f[k] & 0x80) >> 7;
+ caid = (f[k] & 0x70) >> 4;
+ ncomp = f[k] & 0x0F;
+
+ if (fig0.pd() == 0)
+ sprintf(desc,
+ "Service ID=0x%X (Country id=%d, Service reference=%d), Number of components=%d, Local flag=%d, CAID=%d",
+ sid, cid, sref, ncomp, local, caid);
+ else
+ sprintf(desc,
+ "Service ID=0x%X (ECC=%d, Country id=%d, Service reference=%d), Number of components=%d, Local flag=%d, CAID=%d",
+ sid, ecc, cid, sref, ncomp, local, caid);
+ printbuf(desc, indent+1, NULL, 0);
+
+ k++;
+ for (int i=0; i<ncomp; i++) {
+ uint8_t scomp[2];
+
+ memcpy(scomp, f+k, 2);
+ sprintf(desc, "Component[%d]", i);
+ printbuf(desc, indent+2, scomp, 2, "");
+ timd = (scomp[0] & 0xC0) >> 6;
+ ps = (scomp[1] & 0x02) >> 1;
+ ca = scomp[1] & 0x01;
+ scty = scomp[0] & 0x3F;
+ subchid = (scomp[1] & 0xFC) >> 2;
+
+ /* useless, kept as reference
+ if (timd == 3) {
+ uint16_t scid;
+ scid = scty*64 + subchid;
+ }
+ */
+
+ if (ps == 0) {
+ psdesc = "Secondary service";
+ }
+ else {
+ psdesc = "Primary service";
+ }
+
+
+ if (timd == 0) {
+ //MSC stream audio
+ if (scty == 0)
+ sprintf(sctydesc, "MPEG Foreground sound (%d)", scty);
+ else if (scty == 1)
+ sprintf(sctydesc, "MPEG Background sound (%d)", scty);
+ else if (scty == 2)
+ sprintf(sctydesc, "Multi Channel sound (%d)", scty);
+ else if (scty == 63)
+ sprintf(sctydesc, "AAC sound (%d)", scty);
+ else
+ sprintf(sctydesc, "Unknown ASCTy (%d)", scty);
+
+ sprintf(desc, "Stream audio mode, %s, %s, SubChannel ID=%02X, CA=%d", psdesc.c_str(), sctydesc, subchid, ca);
+ printbuf(desc, indent+3, NULL, 0);
+ }
+ else if (timd == 1) {
+ // MSC stream data
+ sprintf(sctydesc, "DSCTy=%d %s", scty, get_dscty_type(scty));
+ sprintf(desc, "Stream data mode, %s, %s, SubChannel ID=%02X, CA=%d", psdesc.c_str(), sctydesc, subchid, ca);
+ printbuf(desc, indent+3, NULL, 0);
+ }
+ else if (timd == 2) {
+ // FIDC
+ sprintf(sctydesc, "DSCTy=%d %s", scty, get_dscty_type(scty));
+ sprintf(desc, "FIDC mode, %s, %s, Fast Information Data Channel ID=%02X, CA=%d", psdesc.c_str(), sctydesc, subchid, ca);
+ printbuf(desc, indent+3, NULL, 0);
+ }
+ else if (timd == 3) {
+ // MSC Packet mode
+ sprintf(desc, "MSC Packet Mode, %s, Service Component ID=%02X, CA=%d", psdesc.c_str(), subchid, ca);
+ printbuf(desc, indent+3, NULL, 0);
+ }
+ k += 2;
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_21.cpp b/src/fig0_21.cpp
new file mode 100644
index 0000000..69f18b9
--- /dev/null
+++ b/src/fig0_21.cpp
@@ -0,0 +1,323 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> regions_seen;
+
+bool fig0_21_is_complete(int region_id)
+{
+ bool complete = regions_seen.count(region_id);
+
+ if (complete) {
+ regions_seen.clear();
+ }
+ else {
+ regions_seen.insert(region_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/21 Frequency Information
+// ETSI EN 300 401 8.1.8
+bool fig0_21(fig0_common_t& fig0, int indent)
+{
+ float freq;
+ uint32_t ifreq;
+ uint64_t key;
+ uint16_t RegionId, Id_field;
+ uint8_t i = 1, j, k, Length_FI_list, RandM, Length_Freq_list, Control_field;
+ uint8_t Control_field_trans_mode, Id_field2;
+ char tmpbuf[256];
+ char desc[256];
+ bool Continuity_flag;
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 1)) {
+ // iterate over frequency information
+ // decode RegionId, Length of FI list
+ RegionId = (f[i] << 3) | (f[i+1] >> 5);
+ complete |= fig0_21_is_complete(RegionId);
+ Length_FI_list = (f[i+1] & 0x1F);
+ sprintf(desc, "RegionId=0x%03x", RegionId);
+ printbuf(desc, indent+1, NULL, 0);
+ i += 2;
+ if ((i + Length_FI_list) <= fig0.figlen) {
+ j = i;
+ while ((j + 2) < (i + Length_FI_list)) {
+ // iterate over FI list x
+ // decode Id field, R&M, Continuity flag, Length of Freq list
+ Id_field = (f[j] << 8) | f[j+1];
+ RandM = f[j+2] >> 4;
+ Continuity_flag = (f[j+2] >> 3) & 0x01;
+ Length_Freq_list = f[j+2] & 0x07;
+ sprintf(desc, "Id_field=");
+ switch (RandM) {
+ case 0x0:
+ case 0x1:
+ strcat(desc, "EId");
+ break;
+ case 0x6:
+ strcat(desc, "DRM Service Id");
+ break;
+ case 0x8:
+ strcat(desc, "RDS PI");
+ break;
+ case 0x9:
+ case 0xa:
+ case 0xc:
+ strcat(desc, "Dummy");
+ break;
+ case 0xe:
+ strcat(desc, "AMSS Service Id");
+ break;
+ default:
+ strcat(desc, "invalid");
+ break;
+ }
+ sprintf(tmpbuf, "=0x%X, R&M=0x%1x", Id_field, RandM);
+ strcat(desc, tmpbuf);
+ switch (RandM) {
+ case 0x0:
+ strcat(desc, "=DAB ensemble, no local windows");
+ break;
+ case 0x6:
+ strcat(desc, "=DRM");
+ break;
+ case 0x8:
+ strcat(desc, "=FM with RDS");
+ break;
+ case 0x9:
+ strcat(desc, "=FM without RDS");
+ break;
+ case 0xa:
+ strcat(desc, "=AM (MW in 9 kHz steps & LW)");
+ break;
+ case 0xc:
+ strcat(desc, "=AM (MW in 5 kHz steps & SW)");
+ break;
+ case 0xe:
+ strcat(desc, "=AMSS");
+ break;
+ default:
+ strcat(desc, "=Rfu");
+ break;
+ }
+ sprintf(tmpbuf, ", Continuity flag=%d", Continuity_flag);
+ strcat(desc, tmpbuf);
+ if ((fig0.oe() == 0) || ((fig0.oe() == 1) && (RandM != 0x6) && \
+ ((RandM < 0x8) || (RandM > 0xa)) && (RandM != 0xc) && (RandM != 0xe))) {
+ if (Continuity_flag == 0) {
+ switch (RandM) {
+ case 0x0:
+ case 0x1:
+ strcat(desc, "=continuous output not expected");
+ break;
+ case 0x6:
+ strcat(desc, "=no compensating time delay on DRM audio signal");
+ break;
+ case 0x8:
+ case 0x9:
+ strcat(desc, "=no compensating time delay on FM audio signal");
+ break;
+ case 0xa:
+ case 0xc:
+ case 0xe:
+ strcat(desc, "=no compensating time delay on AM audio signal");
+ break;
+ default:
+ strcat(desc, "=Rfu");
+ break;
+ }
+ }
+ else { // Continuity_flag == 1
+ switch (RandM) {
+ case 0x0:
+ case 0x1:
+ strcat(desc, "=continuous output possible");
+ break;
+ case 0x6:
+ strcat(desc, "=compensating time delay on DRM audio signal");
+ break;
+ case 0x8:
+ case 0x9:
+ strcat(desc, "=compensating time delay on FM audio signal");
+ break;
+ case 0xa:
+ case 0xc:
+ case 0xe:
+ strcat(desc, "=compensating time delay on AM audio signal");
+ break;
+ default:
+ strcat(desc, "=Rfu");
+ break;
+ }
+ }
+ }
+ else { // fig0.oe() == 1
+ strcat(desc, "=reserved for future addition");
+ }
+ key = ((uint64_t)fig0.oe() << 32) | ((uint64_t)fig0.pd() << 31) | \
+ ((uint64_t)RegionId << 20) | ((uint64_t)Id_field << 4) | \
+ (uint64_t)RandM;
+ sprintf(tmpbuf, ", database key=0x%09" PRId64, key);
+ // CEI Change Event Indication
+ if (Length_Freq_list == 0) {
+ strcat(tmpbuf, ", CEI");
+ }
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+2, NULL, 0);
+ j += 3; // add header
+
+ k = j;
+ switch (RandM) {
+ case 0x0:
+ case 0x1:
+ while((k + 2) < (j + Length_Freq_list)) {
+ // iteration over Freq list
+ ifreq = (((uint32_t)(f[k] & 0x07) << 16) | ((uint32_t)f[k+1] << 8) | (uint32_t)f[k+2]) * 16;
+ if (ifreq != 0) {
+ Control_field = (f[k] >> 3);
+ Control_field_trans_mode = (Control_field >> 1) & 0x07;
+ if ((Control_field & 0x10) == 0) {
+ sprintf(desc, "%d KHz, ", ifreq);
+ if ((Control_field & 0x01) == 0) {
+ strcat(desc, "geographically adjacent area, ");
+ }
+ else { // (Control_field & 0x01) == 1
+ strcat(desc, "no geographically adjacent area, ");
+ }
+ if (Control_field_trans_mode == 0) {
+ strcat(desc, "no transmission mode signalled");
+ }
+ else if (Control_field_trans_mode <= 4) {
+ sprintf(tmpbuf, "transmission mode %d", Control_field_trans_mode);
+ strcat(desc, tmpbuf);
+ }
+ else { // Control_field_trans_mode > 4
+ sprintf(tmpbuf, "invalid transmission mode 0x%x", Control_field_trans_mode);
+ strcat(desc, tmpbuf);
+ }
+ }
+ else { // (Control_field & 0x10) == 0x10
+ sprintf(desc, "%d KHz, invalid Control field b23 0x%x", ifreq, Control_field);
+ }
+ }
+ else {
+ sprintf(desc, "Frequency not to be used (0)");
+ }
+ printbuf(desc, indent+3, NULL, 0);
+ k += 3;
+ }
+ break;
+ case 0x8:
+ case 0x9:
+ case 0xa:
+ while(k < (j + Length_Freq_list)) {
+ // iteration over Freq list
+ if (f[k] != 0) { // freq != 0
+ if (RandM == 0xa) {
+ if (f[k] < 16) {
+ ifreq = (144 + ((uint32_t)f[k] * 9));
+ }
+ else { // f[k] >= 16
+ ifreq = (387 + ((uint32_t)f[k] * 9));
+ }
+ sprintf(desc, "%d KHz", ifreq);
+ }
+ else { // RandM == 8 or 9
+ freq = (87.5 + ((float)f[k] * 0.1));
+ sprintf(desc, "%.1f MHz", freq);
+ }
+ }
+ else {
+ sprintf(desc, "Frequency not to be used (0)");
+ }
+ printbuf(desc, indent+3, NULL, 0);
+ k++;
+ }
+ break;
+ case 0xc:
+ while((k + 1) < (j + Length_Freq_list)) {
+ // iteration over Freq list
+ ifreq = (((uint32_t)f[k] << 8) | (uint32_t)f[k+1]) * 5;
+ if (ifreq != 0) {
+ sprintf(desc, "%d KHz", ifreq);
+ }
+ else {
+ sprintf(desc, "Frequency not to be used (0)");
+ }
+ printbuf(desc, indent+3, NULL, 0);
+ k += 2;
+ }
+ break;
+ case 0x6:
+ case 0xe:
+ while((k + 2) < (j + Length_Freq_list)) {
+ // iteration over Freq list
+ Id_field2 = f[k];
+ ifreq = ((((uint32_t)f[k+1] & 0x7f) << 8) | (uint32_t)f[k+2]);
+ if (ifreq != 0) {
+ sprintf(desc, "%d KHz", ifreq);
+ }
+ else {
+ sprintf(desc, "Frequency not to be used (0)");
+ }
+ if (RandM == 0x6) {
+ sprintf(tmpbuf, ", DRM Service Id 0x%X", Id_field2);
+ strcat(desc, tmpbuf);
+ }
+ else if (RandM == 0xe) {
+ sprintf(tmpbuf, ", AMSS Service Id 0x%X", Id_field2);
+ strcat(desc, tmpbuf);
+ }
+ if ((f[k+1] & 0x80) == 0x80) {
+ sprintf(tmpbuf, ", invalid Rfu b15 set to 1 instead of 0");
+ strcat(desc, tmpbuf);
+ }
+ printbuf(desc, indent+3, NULL, 0);
+ k += 3;
+ }
+ break;
+ default:
+ break;
+ }
+ j += Length_Freq_list;
+ }
+ i += Length_FI_list;
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_22.cpp b/src/fig0_22.cpp
new file mode 100644
index 0000000..dfee52b
--- /dev/null
+++ b/src/fig0_22.cpp
@@ -0,0 +1,186 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> identifiers_seen;
+
+bool fig0_22_is_complete(int M_S, int MainId)
+{
+ int identifier = (M_S << 7) | MainId;
+
+ bool complete = identifiers_seen.count(identifier);
+
+ if (complete) {
+ identifiers_seen.clear();
+ }
+ else {
+ identifiers_seen.insert(identifier);
+ }
+
+ return complete;
+}
+
+
+// map for fig 0/22 database
+std::map<uint16_t, Lat_Lng> fig0_22_key_Lat_Lng;
+
+void fig0_22_cleardb()
+{
+ fig0_22_key_Lat_Lng.clear();
+}
+
+// FIG 0/22 Transmitter Identification Information (TII) database
+// ETSI EN 300 401 8.1.9
+bool fig0_22(fig0_common_t& fig0, int indent)
+{
+ Lat_Lng gps_pos = {0, 0};
+ double latitude_sub, longitude_sub;
+ int16_t Latitude_coarse, Longitude_coarse;
+ uint16_t key, TD;
+ int16_t Latitude_offset, Longitude_offset;
+ uint8_t i = 1, j, MainId = 0, Rfu, Nb_SubId_fields, SubId;
+ uint8_t Latitude_fine, Longitude_fine;
+ char tmpbuf[256];
+ char desc[256];
+ bool MS;
+ const uint8_t Mode_Identity = get_mode_identity();
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < fig0.figlen) {
+ // iterate over Transmitter Identification Information (TII) fields
+ MS = f[i] >> 7;
+ MainId = f[i] & 0x7F;
+ complete |= fig0_22_is_complete(MS, MainId);
+ key = (fig0.oe() << 8) | (fig0.pd() << 7) | MainId;
+ sprintf(desc, "M/S=%d %sidentifier, MainId=0x%X",
+ MS, MS?"Sub-":"Main ", MainId);
+ // check MainId value
+ if ((Mode_Identity == 1) || (Mode_Identity == 2) || (Mode_Identity == 4)) {
+ if (MainId > 69) {
+ // The coding range shall be 0 to 69 for transmission modes I, II and IV
+ sprintf(tmpbuf, " invalid value for transmission mode %d", Mode_Identity);
+ strcat(desc, tmpbuf);
+ }
+ }
+ else if (Mode_Identity == 3) {
+ if (MainId > 5) {
+ // The coding range shall be 0 to 5 for transmission modes I, II and IV
+ sprintf(tmpbuf, " invalid value for transmission mode %d", Mode_Identity);
+ strcat(desc, tmpbuf);
+ }
+ }
+ // print database key
+ sprintf(tmpbuf, ", database key=0x%X", key);
+ strcat(desc, tmpbuf);
+ i++;
+ if (MS == 0) {
+ // Main identifier
+
+ if (i < (fig0.figlen - 4)) {
+ Latitude_coarse = (f[i] << 8) | f[i+1];
+ Longitude_coarse = (f[i+2] << 8) | f[i+3];
+ Latitude_fine = f[i+4] >> 4;
+ Longitude_fine = f[i+4] & 0x0F;
+ gps_pos.latitude = (double)((int32_t)((((int32_t)Latitude_coarse) << 4) | (uint32_t)Latitude_fine)) * 90 / 524288;
+ gps_pos.longitude = (double)((int32_t)((((int32_t)Longitude_coarse) << 4) | (uint32_t)Longitude_fine)) * 180 / 524288;
+ fig0_22_key_Lat_Lng[key] = gps_pos;
+ sprintf(tmpbuf, ", Lat Lng coarse=0x%X 0x%X, Lat Lng fine=0x%X 0x%X => Lat Lng=%f, %f",
+ Latitude_coarse, Longitude_coarse, Latitude_fine, Longitude_fine,
+ gps_pos.latitude, gps_pos.longitude);
+ strcat(desc, tmpbuf);
+ i += 5;
+ }
+ else {
+ strcat(desc, ", invalid length of Latitude Longitude coarse fine");
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ }
+ else { // MS == 1
+ // Sub-identifier
+
+ if (i < fig0.figlen) {
+ Rfu = f[i] >> 3;
+ Nb_SubId_fields = f[i] & 0x07;
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Number of SubId fields=%d%s",
+ Nb_SubId_fields, (Nb_SubId_fields == 0)?", CEI":"");
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+1, NULL, 0);
+ i++;
+
+ for(j = i; ((j < (i + (Nb_SubId_fields * 6))) && (j < (fig0.figlen - 5))); j += 6) {
+ // iterate over SubId fields
+ SubId = f[j] >> 3;
+ sprintf(desc, "SubId=0x%X", SubId);
+ // check SubId value
+ if ((SubId == 0) || (SubId > 23)) {
+ strcat(desc, " invalid value");
+ }
+
+ TD = ((f[j] & 0x03) << 8) | f[j+1];
+ Latitude_offset = (f[j+2] << 8) | f[j+3];
+ Longitude_offset = (f[j+4] << 8) | f[j+5];
+ sprintf(tmpbuf, ", TD=%d us, Lat Lng offset=0x%X 0x%X",
+ TD, Latitude_offset, Longitude_offset);
+ strcat(desc, tmpbuf);
+
+ if (fig0_22_key_Lat_Lng.count(key) > 0) {
+ // latitude longitude available in database for Main Identifier
+ latitude_sub = (90 * (double)Latitude_offset / 524288) + fig0_22_key_Lat_Lng[key].latitude;
+ longitude_sub = (180 * (double)Longitude_offset / 524288) + fig0_22_key_Lat_Lng[key].longitude;
+ sprintf(tmpbuf, " => Lat Lng=%f, %f", latitude_sub, longitude_sub);
+ strcat(desc, tmpbuf);
+ }
+ else {
+ // latitude longitude not available in database for Main Identifier
+ latitude_sub = 90 * (double)Latitude_offset / 524288;
+ longitude_sub = 180 * (double)Longitude_offset / 524288;
+ sprintf(tmpbuf, " => Lat Lng=%f, %f wrong value because Main identifier latitude/longitude not available in database", latitude_sub, longitude_sub);
+ strcat(desc, tmpbuf);
+ }
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ i += (Nb_SubId_fields * 6);
+ }
+ else {
+ strcat(desc, ", invalid fig length or Number of SubId fields length");
+ printbuf(desc, indent+1, NULL, 0);
+ }
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_24.cpp b/src/fig0_24.cpp
new file mode 100644
index 0000000..7867d9e
--- /dev/null
+++ b/src/fig0_24.cpp
@@ -0,0 +1,109 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> services_seen;
+
+bool fig0_24_is_complete(int services_id)
+{
+ bool complete = services_seen.count(services_id);
+
+ if (complete) {
+ services_seen.clear();
+ }
+ else {
+ services_seen.insert(services_id);
+ }
+
+ return complete;
+}
+
+// FIG 0/24 fig0.oe() Services
+// ETSI EN 300 401 8.1.10.2
+bool fig0_24(fig0_common_t& fig0, int indent)
+{
+ uint64_t key;
+ uint32_t SId;
+ uint16_t EId;
+ uint8_t i = 1, j, Number_of_EIds, CAId;
+ char tmpbuf[256];
+ char desc[256];
+ uint8_t* f = fig0.f;
+ bool Rfa;
+ bool complete = false;
+
+ while (i < (fig0.figlen - (((uint8_t)fig0.pd() + 1) * 2))) {
+ // iterate over other ensembles services
+ if (fig0.pd() == 0) {
+ SId = ((uint32_t)f[i] << 8) | (uint32_t)f[i+1];
+ i += 2;
+ }
+ else { // fig0.pd() == 1
+ SId = ((uint32_t)f[i] << 24) | ((uint32_t)f[i+1] << 16) |
+ ((uint32_t)f[i+2] << 8) | (uint32_t)f[i+3];
+ i += 4;
+ }
+ complete |= fig0_24_is_complete(SId);
+ Rfa = (f[i] >> 7);
+ CAId = (f[i] >> 4);
+ Number_of_EIds = (f[i] & 0x0f);
+ key = ((uint64_t)fig0.oe() << 33) | ((uint64_t)fig0.pd() << 32) | \
+ (uint64_t)SId;
+ if (fig0.pd() == 0) {
+ sprintf(desc, "SId=0x%X, CAId=%d, Number of EId=%d, database key=%09" PRId64, SId, CAId, Number_of_EIds, key);
+ }
+ else { // fig0.pd() == 1
+ sprintf(desc, "SId=0x%X, CAId=%d, Number of EId=%d, database key=%09" PRId64, SId, CAId, Number_of_EIds, key);
+ }
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ // CEI Change Event Indication
+ if (Number_of_EIds == 0) {
+ sprintf(tmpbuf, ", CEI");
+ strcat(desc, tmpbuf);
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ i++;
+
+ for(j = i; ((j < (i + (Number_of_EIds * 2))) && (j < fig0.figlen)); j += 2) {
+ // iterate over EIds
+ EId = ((uint16_t)f[j] <<8) | (uint16_t)f[j+1];
+ sprintf(desc, "EId 0x%04x", EId);
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ i += (Number_of_EIds * 2);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_25.cpp b/src/fig0_25.cpp
new file mode 100644
index 0000000..7859868
--- /dev/null
+++ b/src/fig0_25.cpp
@@ -0,0 +1,112 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> services_seen;
+
+bool fig0_25_is_complete(int services_id)
+{
+ bool complete = services_seen.count(services_id);
+
+ if (complete) {
+ services_seen.clear();
+ }
+ else {
+ services_seen.insert(services_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/25 fig0.oe() Announcement support
+// ETSI EN 300 401 8.1.10.5.1
+bool fig0_25(fig0_common_t& fig0, int indent)
+{
+ uint32_t key;
+ uint16_t SId, Asu_flags, EId;
+ uint8_t i = 1, j, Rfu, Number_EIds;
+ char tmpbuf[256];
+ char desc[256];
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 4)) {
+ // iterate over other ensembles announcement support
+ // SId, Asu flags, Rfu, Number of EIds
+ SId = ((uint16_t)f[i] << 8) | (uint16_t)f[i+1];
+ complete |= fig0_25_is_complete(SId);
+ Asu_flags = ((uint16_t)f[i+2] << 8) | (uint16_t)f[i+3];
+ Rfu = (f[i+4] >> 4);
+ Number_EIds = (f[i+4] & 0x0F);
+ sprintf(desc, "SId=0x%X, Asu flags=0x%X", SId, Asu_flags);
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Number of EIds=%d", Number_EIds);
+ strcat(desc, tmpbuf);
+ key = ((uint32_t)fig0.oe() << 17) | ((uint32_t)fig0.pd() << 16) | (uint32_t)SId;
+ sprintf(tmpbuf, ", database key=0x%05x", key);
+ strcat(desc, tmpbuf);
+ // CEI Change Event Indication
+ if (Number_EIds == 0) {
+ sprintf(tmpbuf, ", CEI");
+ strcat(desc, tmpbuf);
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ i += 5;
+
+ for(j = 0; (j < Number_EIds) && (i < (fig0.figlen - 1)); j++) {
+ // iterate over EIds
+ EId = ((uint16_t)f[i] << 8) | (uint16_t)f[i+1];
+ sprintf(desc, "EId=0x%X", EId);
+ printbuf(desc, indent+2, NULL, 0);
+ i += 2;
+ }
+ if (j < Number_EIds) {
+ sprintf(desc, "missing EId, fig length too short !");
+ printbuf(desc, indent+1, NULL, 0);
+ fprintf(stderr, "WARNING: FIG 0/%d length %d too short !\n", fig0.ext(), fig0.figlen);
+ }
+
+ // decode fig0.oe() announcement support types
+ for(j = 0; j < 16; j++) {
+ if (Asu_flags & (1 << j)) {
+ sprintf(desc, "fig0.oe() Announcement support=%s", get_announcement_type(j));
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_26.cpp b/src/fig0_26.cpp
new file mode 100644
index 0000000..c2e4316
--- /dev/null
+++ b/src/fig0_26.cpp
@@ -0,0 +1,109 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> clusters_seen;
+
+bool fig0_26_is_complete(int cluster_id)
+{
+ bool complete = clusters_seen.count(cluster_id);
+
+ if (complete) {
+ clusters_seen.clear();
+ }
+ else {
+ clusters_seen.insert(cluster_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/26 fig0.oe() Announcement switching
+// ETSI EN 300 401 8.1.10.5.2
+bool fig0_26(fig0_common_t& fig0, int indent)
+{
+ uint16_t Asw_flags, EId_Other_Ensemble;
+ uint8_t i = 1, j, Rfa, Cluster_Id_Current_Ensemble, Region_Id_Current_Ensemble;
+ uint8_t Cluster_Id_Other_Ensemble, Region_Id_Other_Ensemble;
+ bool New_flag, Region_flag;
+ char tmpbuf[256];
+ char desc[256];
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 6)) {
+ // iterate over other ensembles announcement switching
+ Cluster_Id_Current_Ensemble = f[i];
+ complete = fig0_26_is_complete(Cluster_Id_Current_Ensemble);
+ Asw_flags = ((uint16_t)f[i+1] << 8) | (uint16_t)f[i+2];
+ New_flag = f[i+3] >> 7;
+ Region_flag = (f[i+3] >> 6) & 0x01;
+ Region_Id_Current_Ensemble = f[i+3] & 0x3F;
+ EId_Other_Ensemble = ((uint16_t)f[i+4] << 8) | (uint16_t)f[i+5];
+ Cluster_Id_Other_Ensemble = f[i+6];
+ sprintf(desc, "Cluster Id Current Ensemble=0x%X, Asw flags=0x%X, New flag=%d %s announcement, Region flag=%d last byte %s, Region Id Current Ensemble=0x%X, EId Other Ensemble=0x%X, Cluster Id Other Ensemble=0x%X",
+ Cluster_Id_Current_Ensemble, Asw_flags, New_flag, New_flag?"newly introduced":"repeated",
+ Region_flag, Region_flag?"present":"absent. The announcement concerns the whole service area",
+ Region_Id_Current_Ensemble, EId_Other_Ensemble, Cluster_Id_Other_Ensemble);
+ i += 7;
+ if (Region_flag != 0) {
+ if (i < fig0.figlen) {
+ // get Region Id Other Ensemble
+ Rfa = (f[i] >> 6);
+ Region_Id_Other_Ensemble = f[i] & 0x3F;
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Region Id Other Ensemble=0x%X", Region_Id_Other_Ensemble);
+ strcat(desc, tmpbuf);
+ }
+ else {
+ sprintf(tmpbuf, "missing Region Id Other Ensemble, fig length too short !");
+ strcat(desc, tmpbuf);
+ fprintf(stderr, "WARNING: FIG 0/%d length %d too short !\n", fig0.ext(), fig0.figlen);
+ }
+ i++;
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ // decode announcement switching types
+ for(j = 0; j < 16; j++) {
+ if (Asw_flags & (1 << j)) {
+ sprintf(desc, "Announcement switching=%s", get_announcement_type(j));
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_27.cpp b/src/fig0_27.cpp
new file mode 100644
index 0000000..1f3f08d
--- /dev/null
+++ b/src/fig0_27.cpp
@@ -0,0 +1,104 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> services_seen;
+
+bool fig0_27_is_complete(int services_id)
+{
+ bool complete = services_seen.count(services_id);
+
+ if (complete) {
+ services_seen.clear();
+ }
+ else {
+ services_seen.insert(services_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/27 FM Announcement support
+// ETSI EN 300 401 8.1.11.2.1
+bool fig0_27(fig0_common_t& fig0, int indent)
+{
+ uint16_t SId, PI;
+ uint8_t i = 1, j, Rfu, Number_PI_codes, key;
+ char tmpbuf[256];
+ char desc[256];
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 2)) {
+ // iterate over FM announcement support
+ SId = ((uint16_t)f[i] << 8) | (uint16_t)f[i+1];
+ complete |= fig0_27_is_complete(SId);
+ Rfu = f[i+2] >> 4;
+ Number_PI_codes = f[i+2] & 0x0F;
+ key = (fig0.oe() << 5) | (fig0.pd() << 4) | Number_PI_codes;
+ sprintf(desc, "SId=0x%X", SId);
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Number of PI codes=%d", Number_PI_codes);
+ strcat(desc, tmpbuf);
+ if (Number_PI_codes > 12) {
+ strcat(desc, " above maximum value of 12");
+ fprintf(stderr, "WARNING: FIG 0/%d Number of PI codes=%d > 12 (maximum value)\n", fig0.ext(), Number_PI_codes);
+ }
+ sprintf(tmpbuf, ", database key=0x%02X", key);
+ strcat(desc, tmpbuf);
+ // CEI Change Event Indication
+ if (Number_PI_codes == 0) {
+ // The Change Event Indication (CEI) is signalled by the Number of PI codes field = 0
+ strcat(desc, ", CEI");
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ i += 3;
+ for(j = 0; (j < Number_PI_codes) && (i < (fig0.figlen - 1)); j++) {
+ // iterate over PI
+ PI = ((uint16_t)f[i] << 8) | (uint16_t)f[i+1];
+ sprintf(desc, "PI=0x%X", PI);
+ printbuf(desc, indent+2, NULL, 0);
+ i += 2;
+ }
+ if (j != Number_PI_codes) {
+ sprintf(desc, "fig length too short !");
+ printbuf(desc, indent+2, NULL, 0);
+ fprintf(stderr, "WARNING: FIG 0/%d length %d too short !\n", fig0.ext(), fig0.figlen);
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_28.cpp b/src/fig0_28.cpp
new file mode 100644
index 0000000..68798c4
--- /dev/null
+++ b/src/fig0_28.cpp
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> clusters_seen;
+
+bool fig0_28_is_complete(int cluster_id)
+{
+ bool complete = clusters_seen.count(cluster_id);
+
+ if (complete) {
+ clusters_seen.clear();
+ }
+ else {
+ clusters_seen.insert(cluster_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/28 FM Announcement switching
+// ETSI EN 300 401 8.1.11.2.2
+bool fig0_28(fig0_common_t& fig0, int indent)
+{
+ uint16_t PI;
+ uint8_t i = 1, Cluster_Id_Current_Ensemble, Region_Id_Current_Ensemble;
+ bool New_flag, Rfa;
+ char tmpbuf[256];
+ char desc[256];
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - 3)) {
+ // iterate over FM announcement switching
+ Cluster_Id_Current_Ensemble = f[i];
+ complete = fig0_28_is_complete(Cluster_Id_Current_Ensemble);
+ New_flag = f[i+1] >> 7;
+ Rfa = (f[i+1] >> 6) & 0x01;
+ Region_Id_Current_Ensemble = f[i+1] & 0x3F;
+ PI = ((uint16_t)f[i+2] << 8) | (uint16_t)f[i+3];
+ sprintf(desc, "Cluster Id Current Ensemble=0x%X", Cluster_Id_Current_Ensemble);
+ if (Cluster_Id_Current_Ensemble == 0) {
+ strcat(desc, " invalid value");
+ fprintf(stderr, "WARNING: FIG 0/%d Cluster Id Current Ensemble invalid value 0\n", fig0.ext());
+ }
+ sprintf(tmpbuf, ", New flag=%d %s announcement",
+ New_flag, New_flag?"newly introduced":"repeated");
+ strcat(desc, tmpbuf);
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", Region Id Current Ensemble=0x%X, PI=0x%X", Region_Id_Current_Ensemble, PI);
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+1, NULL, 0);
+ i += 4;
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_3.cpp b/src/fig0_3.cpp
new file mode 100644
index 0000000..adc502f
--- /dev/null
+++ b/src/fig0_3.cpp
@@ -0,0 +1,110 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <unordered_set>
+
+static std::unordered_set<int> components_ids_seen;
+
+bool fig0_3_is_complete(int components_id)
+{
+ bool complete = components_ids_seen.count(components_id);
+
+ if (complete) {
+ components_ids_seen.clear();
+ }
+ else {
+ components_ids_seen.insert(components_id);
+ }
+
+ return complete;
+}
+
+
+// FIG 0/3 Service component in packet mode with or without Conditional Access
+// ETSI EN 300 401 6.3.2
+bool fig0_3(fig0_common_t& fig0, int indent)
+{
+ uint16_t SCId, Packet_address, CAOrg;
+ uint8_t i = 1, Rfa, DSCTy, SubChId, CAMode, SharedFlag;
+ char tmpbuf[256];
+ char desc[256];
+ bool CAOrg_flag, DG_flag, Rfu;
+ bool complete = false;
+
+ uint8_t* f = fig0.f;
+
+ while (i < (fig0.figlen - 4)) {
+ // iterate over service component in packet mode
+ SCId = ((uint16_t)f[i] << 4) | ((uint16_t)(f[i+1] >> 4) & 0x0F);
+ complete |= fig0_3_is_complete(SCId);
+ Rfa = (f[i+1] >> 1) & 0x07;
+ CAOrg_flag = f[i+1] & 0x01;
+ DG_flag = (f[i+2] >> 7) & 0x01;
+ Rfu = (f[i+2] >> 6) & 0x01;
+ DSCTy = f[i+2] & 0x3F;
+ SubChId = (f[i+3] >> 2);
+ Packet_address = ((uint16_t)(f[i+3] & 0x03) << 8) | ((uint16_t)f[i+4]);
+ sprintf(desc,
+ "SCId=0x%X, CAOrg flag=%d CAOrg field %s, DG flag=%d"
+ " data groups are %sused to transport the service component,"
+ " DSCTy=%d %s, SubChId=0x%X, Packet address=0x%X",
+ SCId, CAOrg_flag, CAOrg_flag?"present":"absent", DG_flag,
+ DG_flag ? "not ": "",
+ DSCTy, get_dscty_type(DSCTy), SubChId,
+ Packet_address);
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ if (Rfu != 0) {
+ sprintf(tmpbuf, ", Rfu=%d invalid value", Rfu);
+ strcat(desc, tmpbuf);
+ }
+ i += 5;
+ if (CAOrg_flag) {
+ if (i < (fig0.figlen - 1)) {
+ CAOrg = ((uint16_t)f[i] << 8) | ((uint16_t)f[i+1]);
+ CAMode = (f[i] >> 5);
+ SharedFlag = f[i+1];
+ sprintf(tmpbuf, ", CAOrg=0x%X CAMode=%d \"%s\" SharedFlag=0x%X%s",
+ CAOrg, CAMode, get_ca_mode(CAMode), SharedFlag, (SharedFlag == 0) ? " invalid" : "");
+ strcat(desc, tmpbuf);
+ }
+ else {
+ sprintf(tmpbuf, ", invalid figlen");
+ strcat(desc, tmpbuf);
+ }
+ i += 2;
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_31.cpp b/src/fig0_31.cpp
new file mode 100644
index 0000000..cbaf296
--- /dev/null
+++ b/src/fig0_31.cpp
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<uint64_t> figtype_flags_seen;
+
+bool fig0_31_is_complete(uint64_t figtype_flags)
+{
+ bool complete = figtype_flags_seen.count(figtype_flags);
+
+ if (complete) {
+ figtype_flags_seen.clear();
+ }
+ else {
+ figtype_flags_seen.insert(figtype_flags);
+ }
+
+ return complete;
+}
+
+// FIG 0/31 FIC re-direction
+// ETSI EN 300 401 8.1.12
+bool fig0_31(fig0_common_t& fig0, int indent)
+{
+ uint32_t FIG_type0_flag_field = 0, flag_field;
+ uint8_t i = 1, j, FIG_type1_flag_field = 0, FIG_type2_flag_field = 0;
+ char desc[256];
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ if (i < (fig0.figlen - 5)) {
+ // Read FIC re-direction
+ FIG_type0_flag_field = ((uint32_t)f[i] << 24) | ((uint32_t)f[i+1] << 16) |
+ ((uint32_t)f[i+2] << 8) | (uint32_t)f[i+3];
+ FIG_type1_flag_field = f[i+4];
+ FIG_type2_flag_field = f[i+5];
+
+ uint64_t key = ((uint64_t)FIG_type1_flag_field << 32) | ((uint64_t)FIG_type2_flag_field << 40) | FIG_type0_flag_field;
+ complete |= fig0_31_is_complete(key);
+
+ sprintf(desc, "FIG type 0 flag field=0x%X, FIG type 1 flag field=0x%X, FIG type 2 flag field=0x%X",
+ FIG_type0_flag_field, FIG_type1_flag_field, FIG_type2_flag_field);
+ printbuf(desc, indent+1, NULL, 0);
+
+ for(j = 0; j < 32; j++) {
+ // iterate over FIG type 0 re-direction
+ flag_field = FIG_type0_flag_field & ((uint32_t)1 << j);
+ if ((flag_field != 0) && ((j <= 5) || (j == 8) ||
+ (j == 10) || (j == 13) || (j == 14) ||
+ (j == 19) || (j == 26) || (j == 28))) {
+ sprintf(desc, "fig0.oe()=%d FIG 0/%d carried in AIC, invalid configuration, shall always be carried entirely in the FIC",
+ fig0.oe(), j);
+ printbuf(desc, indent+2, NULL, 0);
+ fprintf(stderr, "WARNING: FIG 0/%d FIG re-direction of fig0.oe()=%d FIG0/%d not allowed\n", fig0.ext(), fig0.oe(), j);
+ }
+ else if ((flag_field != 0) && ((j == 21) || (j == 24))) {
+ sprintf(desc, "fig0.oe()=%d FIG 0/%d carried in AIC, same shall be carried in FIC", fig0.oe(), j);
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ else if (flag_field != 0) {
+ if (fig0.oe() == 0) {
+ sprintf(desc, "fig0.oe()=%d FIG 0/%d carried in AIC, same shall be carried in FIC", fig0.oe(), j);
+ }
+ else { // fig0.oe() == 1
+ sprintf(desc, "fig0.oe()=%d FIG 0/%d carried in AIC, may be carried entirely in AIC", fig0.oe(), j);
+ }
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+
+ for(j = 0; j < 8; j++) {
+ // iterate over FIG type 1 re-direction
+ flag_field = FIG_type1_flag_field & ((uint32_t)1 << j);
+ if (flag_field != 0) {
+ if (fig0.oe() == 0) {
+ sprintf(desc, "fig0.oe()=%d FIG 1/%d carried in AIC, same shall be carried in FIC", fig0.oe(), j);
+ }
+ else { // fig0.oe() == 1
+ sprintf(desc, "fig0.oe()=%d FIG 1/%d carried in AIC, may be carried entirely in AIC", fig0.oe(), j);
+ }
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+
+ for(j = 0; j < 8; j++) {
+ // iterate over FIG type 2 re-direction
+ flag_field = FIG_type2_flag_field & ((uint32_t)1 << j);
+ if (flag_field != 0) {
+ if (fig0.oe() == 0) {
+ sprintf(desc, "fig0.oe()=%d FIG 2/%d carried in AIC, same shall be carried in FIC", fig0.oe(), j);
+ }
+ else { // fig0.oe() == 1
+ sprintf(desc, "fig0.oe()=%d FIG 2/%d carried in AIC, may be carried entirely in AIC", fig0.oe(), j);
+ }
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+ }
+ if (fig0.figlen != 7) {
+ fprintf(stderr, "WARNING: FIG 0/%d invalid length %d, expecting 7\n", fig0.ext(), fig0.figlen);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_5.cpp b/src/fig0_5.cpp
new file mode 100644
index 0000000..8d966a6
--- /dev/null
+++ b/src/fig0_5.cpp
@@ -0,0 +1,114 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> components_seen;
+
+bool fig0_5_is_complete(int components_id)
+{
+ bool complete = components_seen.count(components_id);
+
+ if (complete) {
+ components_seen.clear();
+ }
+ else {
+ components_seen.insert(components_id);
+ }
+
+ return complete;
+}
+
+// FIG 0/5 Service component language
+// ETSI EN 300 401 8.1.2
+bool fig0_5(fig0_common_t& fig0, int indent)
+{
+ uint16_t SCId;
+ uint8_t i = 1, SubChId, FIDCId, Language, Rfa;
+ char tmpbuf[256];
+ char desc[256];
+ bool LS_flag, MSC_FIC_flag;
+ bool complete = false;
+
+ uint8_t* f = fig0.f;
+
+ while (i < (fig0.figlen - 1)) {
+ // iterate over service component language
+ LS_flag = f[i] >> 7;
+ if (LS_flag == 0) {
+ // Short form (L/S = 0)
+ MSC_FIC_flag = (f[i] >> 6) & 0x01;
+ Language = f[i+1];
+ if (MSC_FIC_flag == 0) {
+ // 0: MSC in Stream mode and SubChId identifies the sub-channel
+ SubChId = f[i] & 0x3F;
+ sprintf(desc, "L/S flag=%d short form, MSC/FIC flag=%d MSC, SubChId=0x%X, Language=0x%X %s",
+ LS_flag, MSC_FIC_flag, SubChId, Language,
+ get_language_name(Language));
+ }
+ else {
+ // 1: FIC and FIDCId identifies the component
+ FIDCId = f[i] & 0x3F;
+ sprintf(desc, "L/S flag=%d short form, MSC/FIC flag=%d FIC, FIDCId=0x%X, Language=0x%X %s",
+ LS_flag, MSC_FIC_flag, FIDCId, Language,
+ get_language_name(Language));
+ }
+
+ int key = (MSC_FIC_flag << 7) | (f[i] % 0x3F);
+ complete |= fig0_5_is_complete(key);
+ printbuf(desc, indent+1, NULL, 0);
+ i += 2;
+ }
+ else {
+ // Long form (L/S = 1)
+ if (i < (fig0.figlen - 2)) {
+ Rfa = (f[i] >> 4) & 0x07;
+ SCId = (((uint16_t)f[i] & 0x0F) << 8) | (uint16_t)f[i+1];
+ int key = (LS_flag << 15) | SCId;
+ complete |= fig0_5_is_complete(key);
+ Language = f[i+2];
+ sprintf(desc, "L/S flag=%d long form", LS_flag);
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", SCId=0x%X, Language=0x%X %s",
+ SCId, Language,
+ get_language_name(Language));
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+1, NULL, 0);
+ }
+ i += 3;
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_6.cpp b/src/fig0_6.cpp
new file mode 100644
index 0000000..dc35640
--- /dev/null
+++ b/src/fig0_6.cpp
@@ -0,0 +1,190 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <unordered_set>
+
+static std::unordered_set<int> links_seen;
+
+bool fig0_6_is_complete(int link_key)
+{
+ bool complete = links_seen.count(link_key);
+
+ if (complete) {
+ links_seen.clear();
+ }
+ else {
+ links_seen.insert(link_key);
+ }
+
+ return complete;
+}
+
+// map between fig 0/6 database key and LA to detect activation and deactivation of links
+static std::map<uint16_t, bool> fig0_6_key_la;
+
+void fig0_6_cleardb()
+{
+ fig0_6_key_la.clear();
+}
+
+// FIG 0/6 Service linking information
+// ETSI EN 300 401 8.1.15
+bool fig0_6(fig0_common_t& fig0, int indent)
+{
+ uint32_t j;
+ uint16_t LSN, key;
+ uint8_t i = 1, Number_of_Ids, IdLQ;
+ char signal_link[256];
+ char desc[256];
+ bool Id_list_flag, LA, SH, ILS, Shd;
+ bool complete = false;
+
+ uint8_t* f = fig0.f;
+
+ while (i < (fig0.figlen - 1)) {
+ // iterate over service linking
+ Id_list_flag = (f[i] >> 7) & 0x01;
+ LA = (f[i] >> 6) & 0x01;
+ SH = (f[i] >> 5) & 0x01;
+ ILS = (f[i] >> 4) & 0x01;
+ LSN = ((f[i] & 0x0F) << 8) | f[i+1];
+ key = (fig0.oe() << 15) | (fig0.pd() << 14) | (SH << 13) | (ILS << 12) | LSN;
+ complete |= fig0_6_is_complete(key);
+ strcpy(signal_link, "");
+ // check activation / deactivation
+ if ((fig0_6_key_la.count(key) > 0) && (fig0_6_key_la[key] != LA)) {
+ if (LA == 0) {
+ strcat(signal_link, " deactivated");
+ }
+ else {
+ strcat(signal_link, " activated");
+ }
+ }
+ fig0_6_key_la[key] = LA;
+ i += 2;
+ if (Id_list_flag == 0) {
+ if (fig0.cn() == 0) { // Id_list_flag=0 && fig0.cn()=0: CEI Change Event Indication
+ strcat(signal_link, " CEI");
+ }
+ sprintf(desc, "Id list flag=%d, LA=%d %s, S/H=%d %s, ILS=%d %s, LSN=%d, database key=0x%04x%s",
+ Id_list_flag, LA, (LA)?"active":"inactive", SH, (SH)?"Hard":"Soft", ILS, (ILS)?"international":"national", LSN, key, signal_link);
+ printbuf(desc, indent+1, NULL, 0);
+ }
+ else { // Id_list_flag == 1
+ if (i < fig0.figlen) {
+ Number_of_Ids = (f[i] & 0x0F);
+ if (fig0.pd() == 0) {
+ IdLQ = (f[i] >> 5) & 0x03;
+ Shd = (f[i] >> 4) & 0x01;
+ sprintf(desc, "Id list flag=%d, LA=%d %s, S/H=%d %s, ILS=%d %s, LSN=%d, database key=0x%04x, IdLQ=%d, Shd=%d %s, Number of Ids=%d%s",
+ Id_list_flag, LA, (LA)?"active":"inactive", SH, (SH)?"Hard":"Soft", ILS, (ILS)?"international":"national", LSN, key, IdLQ, Shd, (Shd)?"b11-8 in 4-F are different services":"single service", Number_of_Ids, signal_link);
+ printbuf(desc, indent+1, NULL, 0);
+ if (ILS == 0) {
+ // read Id list
+ for(j = 0; ((j < Number_of_Ids) && ((i+2+(j*2)) < fig0.figlen)); j++) {
+ // ETSI EN 300 401 8.1.15:
+ // The IdLQ shall not apply to the first entry in the Id list when fig0.oe() = "0" and
+ // when the version number of the type 0 field is set to "0" (using the C/N flag, see clause 5.2.2.1)
+ // ... , the first entry in the Id list of each Service linking field shall be
+ // the SId that applies to the service in the ensemble.
+ if (((j == 0) && (fig0.oe() == 0) && (fig0.cn() == 0)) ||
+ (IdLQ == 0)) {
+ sprintf(desc, "DAB SId 0x%X", ((f[i+1+(j*2)] << 8) | f[i+2+(j*2)]));
+ }
+ else if (IdLQ == 1) {
+ sprintf(desc, "RDS PI 0x%X", ((f[i+1+(j*2)] << 8) | f[i+2+(j*2)]));
+ }
+ else if (IdLQ == 2) {
+ sprintf(desc, "AM-FM service 0x%X", ((f[i+1+(j*2)] << 8) | f[i+2+(j*2)]));
+ }
+ else { // IdLQ == 3
+ sprintf(desc, "invalid ILS IdLQ configuration");
+ }
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ // check deadlink
+ if ((Number_of_Ids == 0) && (IdLQ == 1)) {
+ sprintf(desc, "deadlink");
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ i += (Number_of_Ids * 2) + 1;
+ }
+ else { // fig0.pd() == 0 && ILS == 1
+ // read Id list
+ for(j = 0; ((j < Number_of_Ids) && ((i+3+(j*3)) < fig0.figlen)); j++) {
+ // ETSI EN 300 401 8.1.15:
+ // The IdLQ shall not apply to the first entry in the Id list when fig0.oe() = "0" and
+ // when the version number of the type 0 field is set to "0" (using the C/N flag, see clause 5.2.2.1)
+ // ... , the first entry in the Id list of each Service linking field shall be
+ // the SId that applies to the service in the ensemble.
+ if (((j == 0) && (fig0.oe() == 0) && (fig0.cn() == 0)) ||
+ (IdLQ == 0)) {
+ sprintf(desc, "DAB SId ecc 0x%02X Id 0x%04X", f[i+1+(j*3)], ((f[i+2+(j*3)] << 8) | f[i+3+(j*3)]));
+ }
+ else if (IdLQ == 1) {
+ sprintf(desc, "RDS PI ecc 0x%02X Id 0x%04X", f[i+1+(j*3)], ((f[i+2+(j*3)] << 8) | f[i+3+(j*3)]));
+ }
+ else if (IdLQ == 2) {
+ sprintf(desc, "AM-FM service ecc 0x%02X Id 0x%04X", f[i+1+(j*3)], ((f[i+2+(j*3)] << 8) | f[i+3+(j*3)]));
+ }
+ else { // IdLQ == 3
+ sprintf(desc, "DRM/AMSS service ecc 0x%02X Id 0x%04X", f[i+1+(j*3)], ((f[i+2+(j*3)] << 8) | f[i+3+(j*3)]));
+ }
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ // check deadlink
+ if ((Number_of_Ids == 0) && (IdLQ == 1)) {
+ sprintf(desc, "deadlink");
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ i += (Number_of_Ids * 3) + 1;
+ }
+ }
+ else { // fig0.pd() == 1
+ sprintf(desc, "Id list flag=%d, LA=%d %s, S/H=%d %s, ILS=%d %s, LSN=%d, database key=0x%04x, Number of Ids=%d%s",
+ Id_list_flag, LA, (LA)?"active":"inactive", SH, (SH)?"Hard":"Soft", ILS, (ILS)?"international":"national", LSN, key, Number_of_Ids, signal_link);
+ printbuf(desc, indent+1, NULL, 0);
+ if (Number_of_Ids > 0) {
+ // read Id list
+ for(j = 0; ((j < Number_of_Ids) && ((i+4+(j*4)) < fig0.figlen)); j++) {
+ sprintf(desc, "SId 0x%X",
+ ((f[i+1+(j*4)] << 24) | (f[i+2+(j*4)] << 16) | (f[i+3+(j*4)] << 8) | f[i+4+(j*4)]));
+ printbuf(desc, indent+2, NULL, 0);
+ }
+ }
+ i += (Number_of_Ids * 4) + 1;
+ }
+ }
+ }
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_8.cpp b/src/fig0_8.cpp
new file mode 100644
index 0000000..60f2e0c
--- /dev/null
+++ b/src/fig0_8.cpp
@@ -0,0 +1,147 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+#include <set>
+
+/* EN 300 401, 8.1.14.3 Service component label
+ * The combination of the SId and the SCIdS provides a service component
+ * identifier which is valid globally.
+ */
+using SId_t = int;
+using SCIdS_t = int;
+static std::set<std::pair<SId_t, SCIdS_t> > components_seen;
+
+bool fig0_8_is_complete(SId_t SId, SCIdS_t SCIdS)
+{
+ auto key = std::make_pair(SId, SCIdS);
+ bool complete = components_seen.count(key);
+
+ if (complete) {
+ components_seen.clear();
+ }
+ else {
+ components_seen.insert(key);
+ }
+
+ return complete;
+}
+
+
+
+// FIG 0/8 Service component global definition
+// ETSI EN 300 401 6.3.5
+bool fig0_8(fig0_common_t& fig0, int indent)
+{
+ uint32_t SId;
+ uint16_t SCId;
+ uint8_t i = 1, Rfa, SCIdS, SubChId, FIDCId;
+ char tmpbuf[256];
+ char desc[256];
+ bool Ext_flag, LS_flag, MSC_FIC_flag;
+ uint8_t* f = fig0.f;
+ bool complete = false;
+
+ while (i < (fig0.figlen - (2 + (2 * fig0.pd())))) {
+ // iterate over service component global definition
+ if (fig0.pd() == 0) {
+ // Programme services, 16 bit SId
+ SId = (f[i] << 8) | f[i+1];
+ i += 2;
+ }
+ else {
+ // Data services, 32 bit SId
+ SId = ((uint32_t)f[i] << 24) | ((uint32_t)f[i+1] << 16) |
+ ((uint32_t)f[i+2] << 8) | (uint32_t)f[i+3];
+ i += 4;
+ }
+ Ext_flag = f[i] >> 7;
+ Rfa = (f[i] >> 4) & 0x7;
+ SCIdS = f[i] & 0x0F;
+ complete |= fig0_8_is_complete(SId, SCIdS);
+ sprintf(desc, "SId=0x%X, Ext flag=%d 8-bit Rfa %s", SId, Ext_flag, (Ext_flag)?"present":"absent");
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", SCIdS=0x%X", SCIdS);
+ strcat(desc, tmpbuf);
+ i++;
+ if (i < fig0.figlen) {
+ LS_flag = f[i] >> 7;
+ sprintf(tmpbuf, ", L/S flag=%d %s", LS_flag, (LS_flag)?"Long form":"Short form");
+ strcat(desc, tmpbuf);
+ if (LS_flag == 0) {
+ // Short form
+ if (i < (fig0.figlen - Ext_flag)) {
+ MSC_FIC_flag = (f[i] >> 6) & 0x01;
+ if (MSC_FIC_flag == 0) {
+ // MSC in stream mode and SubChId identifies the sub-channel
+ SubChId = f[i] & 0x3F;
+ sprintf(tmpbuf, ", MSC/FIC flag=%d MSC, SubChId=0x%X", MSC_FIC_flag, SubChId);
+ strcat(desc, tmpbuf);
+ }
+ else {
+ // FIC and FIDCId identifies the component
+ FIDCId = f[i] & 0x3F;
+ sprintf(tmpbuf, ", MSC/FIC flag=%d FIC, FIDCId=0x%X", MSC_FIC_flag, FIDCId);
+ strcat(desc, tmpbuf);
+ }
+ if (Ext_flag == 1) {
+ // Rfa field present
+ Rfa = f[i+1];
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=0x%X invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ }
+ }
+ i += (1 + Ext_flag);
+ }
+ else {
+ // Long form
+ if (i < (fig0.figlen - 1)) {
+ Rfa = (f[i] >> 4) & 0x07;
+ SCId = (((uint16_t)f[i] & 0x0F) << 8) | (uint16_t)f[i+1];
+ if (Rfa != 0) {
+ sprintf(tmpbuf, ", Rfa=%d invalid value", Rfa);
+ strcat(desc, tmpbuf);
+ }
+ sprintf(tmpbuf, ", SCId=0x%X", SCId);
+ strcat(desc, tmpbuf);
+ }
+ i += 2;
+ }
+ }
+ printbuf(desc, indent+1, NULL, 0);
+ }
+
+ return complete;
+}
+
diff --git a/src/fig0_9.cpp b/src/fig0_9.cpp
new file mode 100644
index 0000000..5c8271c
--- /dev/null
+++ b/src/fig0_9.cpp
@@ -0,0 +1,133 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <cstring>
+#include <map>
+
+// fig 0/9 global variables
+uint8_t Ensemble_ECC=0;
+int8_t Ensemble_LTO=0;
+bool LTO_uniq;
+
+
+// FIG 0/9 Country, LTO and International table
+// ETSI EN 300 401 8.1.3.2
+bool fig0_9(fig0_common_t& fig0, int indent)
+{
+ uint32_t SId;
+ uint8_t i = 1, j, key, Number_of_services, ECC;
+ int8_t LTO;
+ char tmpbuf[256];
+ char desc[256];
+ bool Ext_flag;
+ uint8_t* f = fig0.f;
+
+ if (i < (fig0.figlen - 2)) {
+ // get Ensemble LTO, ECC and International Table Id
+ key = ((uint8_t)fig0.oe() << 1) | (uint8_t)fig0.pd();
+ Ext_flag = f[i] >> 7;
+ LTO_uniq = (f[i]>> 6) & 0x01;
+ Ensemble_LTO = f[i] & 0x3F;
+ if (Ensemble_LTO & 0x20) {
+ // negative Ensemble LTO
+ Ensemble_LTO |= 0xC0;
+ }
+ sprintf(desc, "Ext flag=%d extended field %s, LTO uniq=%d %s, Ensemble LTO=0x%X %s%d:%02d",
+ Ext_flag, Ext_flag?"present":"absent", LTO_uniq,
+ LTO_uniq?"several time zones":"one time zone (time specified by Ensemble LTO)",
+ (Ensemble_LTO & 0x3F), (Ensemble_LTO >= 0)?"":"-" , abs(Ensemble_LTO) >> 1, (Ensemble_LTO & 0x01) * 30);
+ if (abs(Ensemble_LTO) > 24) {
+ sprintf(tmpbuf, " out of range -12 hours to +12 hours");
+ strcat(desc, tmpbuf);
+ }
+ Ensemble_ECC = f[i+1];
+ uint8_t International_Table_Id = f[i+2];
+ set_international_table(International_Table_Id);
+ sprintf(tmpbuf, ", Ensemble ECC=0x%X, International Table Id=0x%X, database key=0x%x",
+ Ensemble_ECC, International_Table_Id, key);
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+1, NULL, 0);
+ i += 3;
+ if (Ext_flag == 1) {
+ // extended field present
+ while (i < fig0.figlen) {
+ // iterate over extended sub-field
+ Number_of_services = f[i] >> 6;
+ LTO = f[i] & 0x3F;
+ if (LTO & 0x20) {
+ // negative LTO
+ LTO |= 0xC0;
+ }
+ sprintf(desc, "Number of services=%d, LTO=0x%X %s%d:%02d",
+ Number_of_services, (LTO & 0x3F), (LTO >= 0)?"":"-" , abs(LTO) >> 1, (LTO & 0x01) * 30);
+ if (abs(LTO) > 24) {
+ sprintf(tmpbuf, " out of range -12 hours to +12 hours");
+ strcat(desc, tmpbuf);
+ }
+ // CEI Change Event Indication
+ if ((Number_of_services == 0) && (LTO == 0) /* && (Ext_flag == 1) */) {
+ sprintf(tmpbuf, ", CEI");
+ strcat(desc, tmpbuf);
+ }
+ i++;
+ if (fig0.pd() == 0) {
+ // Programme services, 16 bit SId
+ if (i < fig0.figlen) {
+ ECC = f[i];
+ sprintf(tmpbuf, ", ECC=0x%X", ECC);
+ strcat(desc, tmpbuf);
+ printbuf(desc, indent+2, NULL, 0);
+ i++;
+ for(j = i; ((j < (i + (Number_of_services * 2))) && (j < fig0.figlen)); j += 2) {
+ // iterate over SId
+ SId = ((uint32_t)f[j] << 8) | (uint32_t)f[j+1];
+ sprintf(desc, "SId 0x%X", SId);
+ printbuf(desc, indent+3, NULL, 0);
+ }
+ i += (Number_of_services * 2);
+ }
+ }
+ else {
+ // Data services, 32 bit SId
+ printbuf(desc, indent+2, NULL, 0);
+ for(j = i; ((j < (i + (Number_of_services * 4))) && (j < fig0.figlen)); j += 4) {
+ // iterate over SId
+ SId = ((uint32_t)f[j] << 24) | ((uint32_t)f[j+1] << 16) |
+ ((uint32_t)f[j+2] << 8) | (uint32_t)f[j+3];
+ sprintf(desc, "SId 0x%X", SId);
+ printbuf(desc, indent+3, NULL, 0);
+ }
+ i += (Number_of_services * 4);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
diff --git a/src/fig1.cpp b/src/fig1.cpp
new file mode 100644
index 0000000..6bb095c
--- /dev/null
+++ b/src/fig1.cpp
@@ -0,0 +1,157 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <cstdio>
+#include <string>
+#include <cstring>
+
+using namespace std;
+
+// SHORT LABELS
+bool fig1_select(fig1_common_t& fig1, int indent)
+{
+ uint16_t ext,oe,charset;
+ uint16_t flag;
+ char label[17];
+ char desc[256];
+ uint8_t* f = fig1.f;
+ const int figtype = 1;
+
+ charset = (f[0] & 0xF0) >> 4;
+ oe = (f[0] & 0x08) >> 3;
+ ext = f[0] & 0x07;
+ sprintf(desc,
+ "FIG %d/%d: OE=%d, Charset=%d",
+ figtype, ext, oe, charset);
+
+ printbuf(desc, indent, f+1, fig1.figlen-1);
+ memcpy(label, f+fig1.figlen-18, 16);
+ label[16] = 0x00;
+ flag = f[fig1.figlen-2] * 256 + \
+ f[fig1.figlen-1];
+
+ switch (ext) {
+ case 0: // FIG 1/0 Ensemble label
+ { // ETSI EN 300 401 8.1.13
+ uint16_t eid;
+ eid = f[1] * 256 + f[2];
+ sprintf(desc, "Ensemble ID 0x%04X label: \"%s\", Short label mask: 0x%04X", eid, label, flag);
+ printinfo(desc, indent+1, 1);
+ }
+ break;
+
+ case 1: // FIG 1/1 Programme service label
+ { // ETSI EN 300 401 8.1.14.1
+ uint16_t sid;
+ sid = f[1] * 256 + f[2];
+ sprintf(desc, "Service ID 0x%X label: \"%s\", Short label mask: 0x%04X", sid, label, flag);
+ printinfo(desc, indent+1, 1);
+ }
+ break;
+
+ case 4: // FIG 1/4 Service component label
+ { // ETSI EN 300 401 8.1.14.3
+ uint32_t sid;
+ uint8_t pd, SCIdS;
+ pd = (f[1] & 0x80) >> 7;
+ SCIdS = f[1] & 0x0F;
+ if (pd == 0) {
+ sid = f[2] * 256 + \
+ f[3];
+ }
+ else {
+ sid = f[2] * 256 * 256 * 256 + \
+ f[3] * 256 * 256 + \
+ f[4] * 256 + \
+ f[5];
+ }
+ sprintf(desc,
+ "Service ID 0x%X , Service Component ID 0x%04X Short, label: \"%s\", label mask: 0x%04X",
+ sid, SCIdS, label, flag);
+ printinfo(desc, indent+1, 1);
+ }
+ break;
+
+ case 5: // FIG 1/5 Data service label
+ { // ETSI EN 300 401 8.1.14.2
+ uint32_t sid;
+ sid = f[1] * 256 * 256 * 256 + \
+ f[2] * 256 * 256 + \
+ f[3] * 256 + \
+ f[4];
+
+ sprintf(desc,
+ "Service ID 0x%X label: \"%s\", Short label mask: 0x%04X",
+ sid, label, flag);
+ printinfo(desc, indent+1, 1);
+ }
+ break;
+
+
+ case 6: // FIG 1/6 X-PAD user application label
+ { // ETSI EN 300 401 8.1.14.4
+ uint32_t sid;
+ uint8_t pd, SCIdS, xpadapp;
+ string xpadappdesc;
+
+ pd = (f[1] & 0x80) >> 7;
+ SCIdS = f[1] & 0x0F;
+ if (pd == 0) {
+ sid = f[2] * 256 + \
+ f[3];
+ xpadapp = f[4] & 0x1F;
+ }
+ else {
+ sid = f[2] * 256 * 256 * 256 + \
+ f[3] * 256 * 256 + \
+ f[4] * 256 + \
+ f[5];
+ xpadapp = f[6] & 0x1F;
+ }
+
+ if (xpadapp == 2) {
+ xpadappdesc = "DLS";
+ }
+ else if (xpadapp == 12) {
+ xpadappdesc = "MOT";
+ }
+ else {
+ xpadappdesc = "?";
+ }
+
+
+ sprintf(desc,"Service ID 0x%X , Service Component ID 0x%04X Short, X-PAD App %02X (%s), label: \"%s\", label mask: 0x%04X",
+ sid, SCIdS, xpadapp, xpadappdesc.c_str(), label, flag);
+ printbuf(desc,indent+1,NULL,0,"");
+ }
+ break;
+ }
+
+ // FIG1s always contain a complete set of information
+ return true;
+}
+
diff --git a/src/figs.cpp b/src/figs.cpp
new file mode 100644
index 0000000..a1557d7
--- /dev/null
+++ b/src/figs.cpp
@@ -0,0 +1,114 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "figs.hpp"
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <sstream>
+#include <time.h>
+#include "utils.hpp"
+
+
+static uint8_t Mode_Identity = 0;
+
+void set_mode_identity(uint8_t mid)
+{
+ Mode_Identity = mid;
+}
+
+uint8_t get_mode_identity()
+{
+ return Mode_Identity;
+}
+
+static size_t International_Table_Id;
+
+size_t get_international_table(void)
+{
+ return International_Table_Id;
+}
+
+void set_international_table(size_t intl_table)
+{
+ International_Table_Id = intl_table;
+}
+
+
+void figs_cleardb()
+{
+ // remove elements from fig0_6_key_la and fig0_22_key_Lat_Lng map containers
+ fig0_22_cleardb();
+ fig0_6_cleardb();
+}
+
+
+bool fig0_select(fig0_common_t& fig0, int indent)
+{
+ switch (fig0.ext()) {
+ case 0: return fig0_0(fig0, indent); break;
+ case 1: return fig0_1(fig0, indent); break;
+ case 2: return fig0_2(fig0, indent); break;
+ case 3: return fig0_3(fig0, indent); break;
+ case 5: return fig0_5(fig0, indent); break;
+ case 6: return fig0_6(fig0, indent); break;
+ case 8: return fig0_8(fig0, indent); break;
+ case 9: return fig0_9(fig0, indent); break;
+ case 10: return fig0_10(fig0, indent); break;
+ case 11: return fig0_11(fig0, indent); break;
+ case 13: return fig0_13(fig0, indent); break;
+ case 14: return fig0_14(fig0, indent); break;
+ case 16: return fig0_16(fig0, indent); break;
+ case 17: return fig0_17(fig0, indent); break;
+ case 18: return fig0_18(fig0, indent); break;
+ case 19: return fig0_19(fig0, indent); break;
+ case 21: return fig0_21(fig0, indent); break;
+ case 22: return fig0_22(fig0, indent); break;
+ case 24: return fig0_24(fig0, indent); break;
+ case 25: return fig0_25(fig0, indent); break;
+ case 26: return fig0_26(fig0, indent); break;
+ case 27: return fig0_27(fig0, indent); break;
+ case 28: return fig0_28(fig0, indent); break;
+ case 31: return fig0_31(fig0, indent); break;
+ default: {
+ char desc[256];
+ sprintf(desc, "FIG 0/%d: unknown", fig0.ext());
+ printbuf(desc, indent, fig0.f+1, fig0.figlen-1);
+ break;
+ }
+ }
+
+ return false;
+}
+
diff --git a/src/figs.hpp b/src/figs.hpp
new file mode 100644
index 0000000..7bd5fd0
--- /dev/null
+++ b/src/figs.hpp
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#pragma once
+
+#include <cstdint>
+#include "utils.hpp"
+#include "tables.hpp"
+#include "watermarkdecoder.hpp"
+
+void figs_cleardb(void);
+
+struct fig0_common_t {
+ fig0_common_t(
+ uint8_t* fig_data,
+ uint16_t fig_len,
+ WatermarkDecoder &wm_dec) :
+ f(fig_data),
+ figlen(fig_len),
+ wm_decoder(wm_dec) { }
+
+ uint8_t* f;
+ uint16_t figlen;
+ WatermarkDecoder &wm_decoder;
+
+ uint16_t cn(void) { return (f[0] & 0x80) >> 7; }
+ uint16_t oe(void) { return (f[0] & 0x40) >> 6; }
+ uint16_t pd(void) { return (f[0] & 0x20) >> 5; }
+ uint16_t ext(void) { return f[0] & 0x1F; }
+};
+
+struct fig1_common_t {
+ fig1_common_t(
+ uint8_t* fig_data,
+ uint16_t fig_len) :
+ f(fig_data),
+ figlen(fig_len) {}
+
+ uint8_t* f;
+ uint16_t figlen;
+
+ uint8_t charset() { return (f[0] & 0xF0) >> 4; }
+ uint8_t oe() { return (f[0] & 0x08) >> 3; }
+ uint8_t ext() { return f[0] & 0x07; }
+};
+
+// FIG 0/11 and 0/22 struct
+struct Lat_Lng {
+ double latitude, longitude;
+};
+
+// Which international table has been chosen
+size_t get_international_table(void);
+void set_international_table(size_t intl_table);
+
+// MID is used by some FIGs. It is signalled in LIDATA - FC - MID
+void set_mode_identity(uint8_t mid);
+uint8_t get_mode_identity();
+
+bool fig0_select(fig0_common_t& fig0, int indent);
+
+bool fig0_0(fig0_common_t& fig0, int indent);
+bool fig0_1(fig0_common_t& fig0, int indent);
+bool fig0_2(fig0_common_t& fig0, int indent);
+bool fig0_3(fig0_common_t& fig0, int indent);
+bool fig0_5(fig0_common_t& fig0, int indent);
+void fig0_6_cleardb();
+bool fig0_6(fig0_common_t& fig0, int indent);
+bool fig0_8(fig0_common_t& fig0, int indent);
+bool fig0_9(fig0_common_t& fig0, int indent);
+bool fig0_10(fig0_common_t& fig0, int indent);
+bool fig0_11(fig0_common_t& fig0, int indent);
+bool fig0_13(fig0_common_t& fig0, int indent);
+bool fig0_14(fig0_common_t& fig0, int indent);
+bool fig0_16(fig0_common_t& fig0, int indent);
+bool fig0_17(fig0_common_t& fig0, int indent);
+bool fig0_18(fig0_common_t& fig0, int indent);
+bool fig0_19(fig0_common_t& fig0, int indent);
+bool fig0_21(fig0_common_t& fig0, int indent);
+void fig0_22_cleardb();
+bool fig0_22(fig0_common_t& fig0, int indent);
+bool fig0_24(fig0_common_t& fig0, int indent);
+bool fig0_25(fig0_common_t& fig0, int indent);
+bool fig0_26(fig0_common_t& fig0, int indent);
+bool fig0_27(fig0_common_t& fig0, int indent);
+bool fig0_28(fig0_common_t& fig0, int indent);
+bool fig0_31(fig0_common_t& fig0, int indent);
+
+bool fig1_select(fig1_common_t& fig1, int indent);
+
diff --git a/src/firecode.c b/src/firecode.c
new file mode 100644
index 0000000..47ee976
--- /dev/null
+++ b/src/firecode.c
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2014 Matthias P. Braendli (http://www.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/>.
+
+ firecode.c
+ Implement a FireCode CRC calculator
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+*/
+
+#include "firecode.h"
+
+uint16_t firecode_crc(uint8_t* buf, size_t size)
+{
+ int crc;
+ int gen_poly;
+
+ crc = 0x0000;
+ gen_poly = 0x782F; // 0111 1000 0010 1111 (16, 14, 13, 12, 11, 5, 3, 2, 1, 0)
+
+ for (size_t len = 0; len < size; len++) {
+ for (int i = 0x80; i != 0; i >>= 1) {
+ if ((crc & 0x8000) != 0) {
+ crc = (crc << 1) ^ gen_poly;
+ }
+ else {
+ crc = crc << 1;
+ }
+ if ((buf[len] & i) != 0) {
+ crc ^= gen_poly;
+ }
+ }
+ }
+
+ return crc & 0xFFFF;
+}
+
diff --git a/src/firecode.h b/src/firecode.h
new file mode 100644
index 0000000..735a302
--- /dev/null
+++ b/src/firecode.h
@@ -0,0 +1,33 @@
+/*
+ Copyright (C) 2014 Matthias P. Braendli (http://www.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/>.
+
+ firecode.c
+ Implement a FireCode CRC calculator
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+*/
+
+#ifndef _FIRECODE_H_
+#define _FIRECODE_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+
+uint16_t firecode_crc(uint8_t* buf, size_t size);
+
+#endif
+
diff --git a/src/lib_crc.c b/src/lib_crc.c
new file mode 100644
index 0000000..26b6b99
--- /dev/null
+++ b/src/lib_crc.c
@@ -0,0 +1,459 @@
+#include "lib_crc.h"
+
+
+
+ /*******************************************************************\
+ * *
+ * Library : lib_crc *
+ * File : lib_crc.c *
+ * Author : Lammert Bies 1999-2008 *
+ * E-mail : info@lammertbies.nl *
+ * Language : ANSI C *
+ * *
+ * *
+ * Description *
+ * =========== *
+ * *
+ * The file lib_crc.c contains the private and public func- *
+ * tions used for the calculation of CRC-16, CRC-CCITT and *
+ * CRC-32 cyclic redundancy values. *
+ * *
+ * *
+ * Dependencies *
+ * ============ *
+ * *
+ * lib_crc.h CRC definitions and prototypes *
+ * *
+ * *
+ * Modification history *
+ * ==================== *
+ * *
+ * Date Version Comment *
+ * *
+ * 2008-04-20 1.16 Added CRC-CCITT calculation for Kermit *
+ * *
+ * 2007-04-01 1.15 Added CRC16 calculation for Modbus *
+ * *
+ * 2007-03-28 1.14 Added CRC16 routine for Sick devices *
+ * *
+ * 2005-12-17 1.13 Added CRC-CCITT with initial 0x1D0F *
+ * *
+ * 2005-05-14 1.12 Added CRC-CCITT with start value 0 *
+ * *
+ * 2005-02-05 1.11 Fixed bug in CRC-DNP routine *
+ * *
+ * 2005-02-04 1.10 Added CRC-DNP routines *
+ * *
+ * 1999-02-21 1.01 Added FALSE and TRUE mnemonics *
+ * *
+ * 1999-01-22 1.00 Initial source *
+ * *
+ \*******************************************************************/
+
+
+
+ /*******************************************************************\
+ * *
+ * #define P_xxxx *
+ * *
+ * The CRC's are computed using polynomials. The coefficients *
+ * for the algorithms are defined by the following constants. *
+ * *
+ \*******************************************************************/
+
+#define P_16 0xA001
+#define P_32 0xEDB88320L
+#define P_CCITT 0x1021
+#define P_DNP 0xA6BC
+#define P_KERMIT 0x8408
+#define P_SICK 0x8005
+
+
+
+ /*******************************************************************\
+ * *
+ * static int crc_tab...init *
+ * static unsigned ... crc_tab...[] *
+ * *
+ * The algorithms use tables with precalculated values. This *
+ * speeds up the calculation dramaticaly. The first time the *
+ * CRC function is called, the table for that specific calcu- *
+ * lation is set up. The ...init variables are used to deter- *
+ * mine if the initialization has taken place. The calculated *
+ * values are stored in the crc_tab... arrays. *
+ * *
+ * The variables are declared static. This makes them invisi- *
+ * ble for other modules of the program. *
+ * *
+ \*******************************************************************/
+
+static int crc_tab16_init = FALSE;
+static int crc_tab32_init = FALSE;
+static int crc_tabccitt_init = FALSE;
+static int crc_tabdnp_init = FALSE;
+static int crc_tabkermit_init = FALSE;
+
+static unsigned short crc_tab16[256];
+static unsigned long crc_tab32[256];
+static unsigned short crc_tabccitt[256];
+static unsigned short crc_tabdnp[256];
+static unsigned short crc_tabkermit[256];
+
+
+
+ /*******************************************************************\
+ * *
+ * static void init_crc...tab(); *
+ * *
+ * Three local functions are used to initialize the tables *
+ * with values for the algorithm. *
+ * *
+ \*******************************************************************/
+
+static void init_crc16_tab( void );
+static void init_crc32_tab( void );
+static void init_crcccitt_tab( void );
+static void init_crcdnp_tab( void );
+static void init_crckermit_tab( void );
+
+
+
+ /*******************************************************************\
+ * *
+ * unsigned short update_crc_ccitt( unsigned long crc, char c ); *
+ * *
+ * The function update_crc_ccitt calculates a new CRC-CCITT *
+ * value based on the previous value of the CRC and the next *
+ * byte of the data to be checked. *
+ * *
+ \*******************************************************************/
+
+unsigned short update_crc_ccitt( unsigned short crc, char c ) {
+
+ unsigned short tmp, short_c;
+
+ short_c = 0x00ff & (unsigned short) c;
+
+ if ( ! crc_tabccitt_init ) init_crcccitt_tab();
+
+ tmp = (crc >> 8) ^ short_c;
+ crc = (crc << 8) ^ crc_tabccitt[tmp];
+
+ return crc;
+
+} /* update_crc_ccitt */
+
+
+
+ /*******************************************************************\
+ * *
+ * unsigned short update_crc_sick( *
+ * unsigned long crc, char c, char prev_byte ); *
+ * *
+ * The function update_crc_sick calculates a new CRC-SICK *
+ * value based on the previous value of the CRC and the next *
+ * byte of the data to be checked. *
+ * *
+ \*******************************************************************/
+
+unsigned short update_crc_sick( unsigned short crc, char c, char prev_byte ) {
+
+ unsigned short short_c, short_p;
+
+ short_c = 0x00ff & (unsigned short) c;
+ short_p = ( 0x00ff & (unsigned short) prev_byte ) << 8;
+
+ if ( crc & 0x8000 ) crc = ( crc << 1 ) ^ P_SICK;
+ else crc = crc << 1;
+
+ crc &= 0xffff;
+ crc ^= ( short_c | short_p );
+
+ return crc;
+
+} /* update_crc_sick */
+
+
+
+ /*******************************************************************\
+ * *
+ * unsigned short update_crc_16( unsigned short crc, char c ); *
+ * *
+ * The function update_crc_16 calculates a new CRC-16 value *
+ * based on the previous value of the CRC and the next byte *
+ * of the data to be checked. *
+ * *
+ \*******************************************************************/
+
+unsigned short update_crc_16( unsigned short crc, char c ) {
+
+ unsigned short tmp, short_c;
+
+ short_c = 0x00ff & (unsigned short) c;
+
+ if ( ! crc_tab16_init ) init_crc16_tab();
+
+ tmp = crc ^ short_c;
+ crc = (crc >> 8) ^ crc_tab16[ tmp & 0xff ];
+
+ return crc;
+
+} /* update_crc_16 */
+
+
+
+ /*******************************************************************\
+ * *
+ * unsigned short update_crc_kermit( unsigned short crc, char c ); *
+ * *
+ * The function update_crc_kermit calculates a new CRC value *
+ * based on the previous value of the CRC and the next byte *
+ * of the data to be checked. *
+ * *
+ \*******************************************************************/
+
+unsigned short update_crc_kermit( unsigned short crc, char c ) {
+
+ unsigned short tmp, short_c;
+
+ short_c = 0x00ff & (unsigned short) c;
+
+ if ( ! crc_tabkermit_init ) init_crckermit_tab();
+
+ tmp = crc ^ short_c;
+ crc = (crc >> 8) ^ crc_tabkermit[ tmp & 0xff ];
+
+ return crc;
+
+} /* update_crc_kermit */
+
+
+
+ /*******************************************************************\
+ * *
+ * unsigned short update_crc_dnp( unsigned short crc, char c ); *
+ * *
+ * The function update_crc_dnp calculates a new CRC-DNP value *
+ * based on the previous value of the CRC and the next byte *
+ * of the data to be checked. *
+ * *
+ \*******************************************************************/
+
+unsigned short update_crc_dnp( unsigned short crc, char c ) {
+
+ unsigned short tmp, short_c;
+
+ short_c = 0x00ff & (unsigned short) c;
+
+ if ( ! crc_tabdnp_init ) init_crcdnp_tab();
+
+ tmp = crc ^ short_c;
+ crc = (crc >> 8) ^ crc_tabdnp[ tmp & 0xff ];
+
+ return crc;
+
+} /* update_crc_dnp */
+
+
+
+ /*******************************************************************\
+ * *
+ * unsigned long update_crc_32( unsigned long crc, char c ); *
+ * *
+ * The function update_crc_32 calculates a new CRC-32 value *
+ * based on the previous value of the CRC and the next byte *
+ * of the data to be checked. *
+ * *
+ \*******************************************************************/
+
+unsigned long update_crc_32( unsigned long crc, char c ) {
+
+ unsigned long tmp, long_c;
+
+ long_c = 0x000000ffL & (unsigned long) c;
+
+ if ( ! crc_tab32_init ) init_crc32_tab();
+
+ tmp = crc ^ long_c;
+ crc = (crc >> 8) ^ crc_tab32[ tmp & 0xff ];
+
+ return crc;
+
+} /* update_crc_32 */
+
+
+
+ /*******************************************************************\
+ * *
+ * static void init_crc16_tab( void ); *
+ * *
+ * The function init_crc16_tab() is used to fill the array *
+ * for calculation of the CRC-16 with values. *
+ * *
+ \*******************************************************************/
+
+static void init_crc16_tab( void ) {
+
+ int i, j;
+ unsigned short crc, c;
+
+ for (i=0; i<256; i++) {
+
+ crc = 0;
+ c = (unsigned short) i;
+
+ for (j=0; j<8; j++) {
+
+ if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ P_16;
+ else crc = crc >> 1;
+
+ c = c >> 1;
+ }
+
+ crc_tab16[i] = crc;
+ }
+
+ crc_tab16_init = TRUE;
+
+} /* init_crc16_tab */
+
+
+
+ /*******************************************************************\
+ * *
+ * static void init_crckermit_tab( void ); *
+ * *
+ * The function init_crckermit_tab() is used to fill the array *
+ * for calculation of the CRC Kermit with values. *
+ * *
+ \*******************************************************************/
+
+static void init_crckermit_tab( void ) {
+
+ int i, j;
+ unsigned short crc, c;
+
+ for (i=0; i<256; i++) {
+
+ crc = 0;
+ c = (unsigned short) i;
+
+ for (j=0; j<8; j++) {
+
+ if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ P_KERMIT;
+ else crc = crc >> 1;
+
+ c = c >> 1;
+ }
+
+ crc_tabkermit[i] = crc;
+ }
+
+ crc_tabkermit_init = TRUE;
+
+} /* init_crckermit_tab */
+
+
+
+ /*******************************************************************\
+ * *
+ * static void init_crcdnp_tab( void ); *
+ * *
+ * The function init_crcdnp_tab() is used to fill the array *
+ * for calculation of the CRC-DNP with values. *
+ * *
+ \*******************************************************************/
+
+static void init_crcdnp_tab( void ) {
+
+ int i, j;
+ unsigned short crc, c;
+
+ for (i=0; i<256; i++) {
+
+ crc = 0;
+ c = (unsigned short) i;
+
+ for (j=0; j<8; j++) {
+
+ if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ P_DNP;
+ else crc = crc >> 1;
+
+ c = c >> 1;
+ }
+
+ crc_tabdnp[i] = crc;
+ }
+
+ crc_tabdnp_init = TRUE;
+
+} /* init_crcdnp_tab */
+
+
+
+ /*******************************************************************\
+ * *
+ * static void init_crc32_tab( void ); *
+ * *
+ * The function init_crc32_tab() is used to fill the array *
+ * for calculation of the CRC-32 with values. *
+ * *
+ \*******************************************************************/
+
+static void init_crc32_tab( void ) {
+
+ int i, j;
+ unsigned long crc;
+
+ for (i=0; i<256; i++) {
+
+ crc = (unsigned long) i;
+
+ for (j=0; j<8; j++) {
+
+ if ( crc & 0x00000001L ) crc = ( crc >> 1 ) ^ P_32;
+ else crc = crc >> 1;
+ }
+
+ crc_tab32[i] = crc;
+ }
+
+ crc_tab32_init = TRUE;
+
+} /* init_crc32_tab */
+
+
+
+ /*******************************************************************\
+ * *
+ * static void init_crcccitt_tab( void ); *
+ * *
+ * The function init_crcccitt_tab() is used to fill the array *
+ * for calculation of the CRC-CCITT with values. *
+ * *
+ \*******************************************************************/
+
+static void init_crcccitt_tab( void ) {
+
+ int i, j;
+ unsigned short crc, c;
+
+ for (i=0; i<256; i++) {
+
+ crc = 0;
+ c = ((unsigned short) i) << 8;
+
+ for (j=0; j<8; j++) {
+
+ if ( (crc ^ c) & 0x8000 ) crc = ( crc << 1 ) ^ P_CCITT;
+ else crc = crc << 1;
+
+ c = c << 1;
+ }
+
+ crc_tabccitt[i] = crc;
+ }
+
+ crc_tabccitt_init = TRUE;
+
+} /* init_crcccitt_tab */
diff --git a/src/lib_crc.h b/src/lib_crc.h
new file mode 100644
index 0000000..ea5ca76
--- /dev/null
+++ b/src/lib_crc.h
@@ -0,0 +1,66 @@
+ /*******************************************************************\
+ * *
+ * Library : lib_crc *
+ * File : lib_crc.h *
+ * Author : Lammert Bies 1999-2008 *
+ * E-mail : info@lammertbies.nl *
+ * Language : ANSI C *
+ * *
+ * *
+ * Description *
+ * =========== *
+ * *
+ * The file lib_crc.h contains public definitions and proto- *
+ * types for the CRC functions present in lib_crc.c. *
+ * *
+ * *
+ * Dependencies *
+ * ============ *
+ * *
+ * none *
+ * *
+ * *
+ * Modification history *
+ * ==================== *
+ * *
+ * Date Version Comment *
+ * *
+ * 2008-04-20 1.16 Added CRC-CCITT routine for Kermit *
+ * *
+ * 2007-04-01 1.15 Added CRC16 calculation for Modbus *
+ * *
+ * 2007-03-28 1.14 Added CRC16 routine for Sick devices *
+ * *
+ * 2005-12-17 1.13 Added CRC-CCITT with initial 0x1D0F *
+ * *
+ * 2005-02-14 1.12 Added CRC-CCITT with initial 0x0000 *
+ * *
+ * 2005-02-05 1.11 Fixed bug in CRC-DNP routine *
+ * *
+ * 2005-02-04 1.10 Added CRC-DNP routines *
+ * *
+ * 2005-01-07 1.02 Changes in tst_crc.c *
+ * *
+ * 1999-02-21 1.01 Added FALSE and TRUE mnemonics *
+ * *
+ * 1999-01-22 1.00 Initial source *
+ * *
+ \*******************************************************************/
+
+
+
+#define CRC_VERSION "1.16"
+
+
+
+#define FALSE 0
+#define TRUE 1
+
+
+
+unsigned short update_crc_16( unsigned short crc, char c );
+unsigned long update_crc_32( unsigned long crc, char c );
+unsigned short update_crc_ccitt( unsigned short crc, char c );
+unsigned short update_crc_dnp( unsigned short crc, char c );
+unsigned short update_crc_kermit( unsigned short crc, char c );
+unsigned short update_crc_sick( unsigned short crc, char c, char prev_byte );
diff --git a/src/repetitionrate.cpp b/src/repetitionrate.cpp
new file mode 100644
index 0000000..970bcfc
--- /dev/null
+++ b/src/repetitionrate.cpp
@@ -0,0 +1,196 @@
+/*
+ Copyright (C) 2016 Matthias P. Braendli (http://www.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/>.
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+
+*/
+
+#include "repetitionrate.hpp"
+#include <vector>
+#include <map>
+#include <set>
+#include <cmath>
+#include <limits>
+
+using namespace std;
+
+const double FRAME_DURATION = 24e-3;
+
+struct FIGTypeExt {
+ int figtype;
+ int figextension;
+
+ bool operator<(const FIGTypeExt& other) const {
+ if (this->figtype == other.figtype) {
+ return this->figextension < other.figextension;
+ }
+ else {
+ return this->figtype < other.figtype ;
+ }
+ }
+};
+
+struct FIGRateInfo {
+ // List of frame numbers in which the FIG is present
+ vector<int> frames_present;
+
+ // List of frame numbers in which a complete DB for that FIG has been sent
+ vector<int> frames_complete;
+
+ // Which FIBs this FIG was seen in
+ set<int> in_fib;
+};
+
+
+static map<FIGTypeExt, FIGRateInfo> fig_rates;
+
+static int current_frame_number = 0;
+static int current_fib = 0;
+
+void rate_announce_fig(int figtype, int figextension, bool complete)
+{
+ FIGTypeExt f = {.figtype = figtype, .figextension = figextension};
+
+ if (fig_rates.count(f) == 0) {
+ FIGRateInfo rate;
+ fig_rates[f] = rate;
+ }
+
+ FIGRateInfo& rate = fig_rates[f];
+
+ rate.frames_present.push_back(current_frame_number);
+ if (complete) {
+ rate.frames_complete.push_back(current_frame_number);
+ }
+ rate.in_fib.insert(current_fib);
+}
+
+// Calculate the minimal, maximum and average repetition rate (FIGs per
+// second).
+void rate_min_max_avg(
+ const std::vector<int>& fig_positions,
+ double* min,
+ double* max,
+ double* avg,
+ bool per_second)
+{
+ double avg_interval =
+ (double)(fig_positions.back() - fig_positions.front()) /
+ (double)(fig_positions.size() - 1);
+
+ int min_delta = std::numeric_limits<int>::max();
+ int max_delta = 0;
+ for (size_t i = 1; i < fig_positions.size(); i++) {
+ const int delta = fig_positions[i] - fig_positions[i-1];
+
+ // Min
+ if (min_delta > delta) {
+ min_delta = delta;
+ }
+
+ // Max
+ if (max_delta < delta) {
+ max_delta = delta;
+ }
+ }
+
+ *avg = avg_interval;
+ *min = min_delta;
+ *max = max_delta;
+
+ if (per_second) {
+ *avg = 1.0 /
+ (*avg * FRAME_DURATION);
+ *min = 1.0 / (*min * FRAME_DURATION);
+ *max = 1.0 / (*min * FRAME_DURATION);
+ }
+
+}
+
+void rate_display_header(bool per_second)
+{
+ if (per_second) {
+ printf("FIG carousel analysis. Format:\n"
+ " min, average, max FIGs per second (total count) - \n"
+ " min, average, max complete FIGs per second (total count)\n");
+ }
+ else {
+ printf("FIG carousel analysis. Format:\n"
+ " min, average, max frames per FIG (total count) - \n"
+ " min, average, max frames per complete FIGs (total count)\n");
+ }
+}
+
+void rate_display_analysis(bool clear, bool per_second)
+{
+ for (auto& fig_rate : fig_rates) {
+ auto& frames_present = fig_rate.second.frames_present;
+ auto& frames_complete = fig_rate.second.frames_complete;
+
+ double min = 0.0;
+ double max = 0.0;
+ double avg = 0.0;
+
+ const size_t n_present = frames_present.size();
+ const size_t n_complete = frames_complete.size();
+ if (n_present >= 2) {
+
+ rate_min_max_avg(frames_present, &min, &max, &avg, per_second);
+
+ printf("FIG%2d/%2d %4.2f %4.2f %4.2f (%5zu)",
+ fig_rate.first.figtype, fig_rate.first.figextension,
+ min, avg, max,
+ n_present);
+
+ if (n_complete >= 2) {
+ rate_min_max_avg(frames_complete, &min, &max, &avg, per_second);
+
+ printf(" - %4.2f %4.2f %4.2f (%5zu)",
+ min, avg, max,
+ n_present);
+ }
+ else {
+ printf(" - None complete");
+ }
+ }
+ else {
+ printf("FIG%2d/%2d 0",
+ fig_rate.first.figtype, fig_rate.first.figextension);
+ }
+
+ printf(" - in FIB(s):");
+ for (auto& fib : fig_rate.second.in_fib) {
+ printf(" %d", fib);
+ }
+ printf("\n");
+
+ }
+
+ if (clear) {
+ fig_rates.clear();
+ }
+}
+
+void rate_new_fib(int fib)
+{
+ if (fib == 0) {
+ current_frame_number++;
+ }
+
+ current_fib = fib;
+}
+
diff --git a/src/repetitionrate.hpp b/src/repetitionrate.hpp
new file mode 100644
index 0000000..41b5b40
--- /dev/null
+++ b/src/repetitionrate.hpp
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2016 Matthias P. Braendli (http://www.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/>.
+
+ Authors:
+ Matthias P. Braendli <matthias@mpb.li>
+
+*/
+
+#pragma once
+#include "utils.hpp"
+
+/* Tell the repetition rate analyser that we have received a given FIG.
+ * The complete flag should be set to true every time a complete
+ * set of information for that FIG has been received
+ */
+void rate_announce_fig(int figtype, int figextension, bool complete);
+
+/* Tell the repetition rate analyser that a new FIB starts.
+ */
+void rate_new_fib(int fib);
+
+/* Print small header to explain rate display
+ * per_second: if true, rates are calculated in FIGs per second.
+ * If false, rate is given in frames per FIG
+ */
+void rate_display_header(bool per_second);
+
+/* Print analysis.
+ * optionally clear all statistics
+ * per_second: if true, rates are calculated in FIGs per second.
+ * If false, rate is given in frames per FIG
+ */
+void rate_display_analysis(bool clear, bool per_second);
+
diff --git a/src/rsdecoder.cpp b/src/rsdecoder.cpp
new file mode 100644
index 0000000..98ccd39
--- /dev/null
+++ b/src/rsdecoder.cpp
@@ -0,0 +1,83 @@
+/*
+ Copyright (C) 2015 Stefan Pöschel
+
+ Copyright (C) 2015 Matthias P. Braendli (http://www.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/>.
+ */
+
+#include <stdexcept>
+#include "rsdecoder.hpp"
+
+RSDecoder::RSDecoder()
+{
+ rs_handle = init_rs_char(8, 0x11D, 0, 1, 10, 135);
+ if(!rs_handle)
+ throw std::runtime_error("RSDecoder: error while init_rs_char");
+}
+
+RSDecoder::~RSDecoder()
+{
+ free_rs_char(rs_handle);
+}
+
+int RSDecoder::DecodeSuperframe(std::vector<uint8_t> &sf, int subch_index)
+{
+ int total_corr_count = 0;
+ bool uncorr_errors = false;
+
+ std::vector<int> errors_per_index(subch_index);
+
+ // process all RS packets
+ for(int i = 0; i < subch_index; i++) {
+ for(int pos = 0; pos < 120; pos++)
+ rs_packet[pos] = sf[pos * subch_index + i];
+
+ // detect errors
+ int corr_count = decode_rs_char(rs_handle, rs_packet, corr_pos, 0);
+ errors_per_index[i] = corr_count;
+
+ if(corr_count == -1) {
+ uncorr_errors = true;
+ }
+ else {
+ total_corr_count += corr_count;
+ }
+
+ // correct errors
+ for(int j = 0; j < corr_count; j++) {
+
+ int pos = corr_pos[j] - 135;
+ if(pos < 0)
+ continue;
+
+ // fprintf(stderr, "j: %d, pos: %d, sf-index: %d\n", j, pos, pos * subch_index + i);
+ sf[pos * subch_index + i] = rs_packet[pos];
+ }
+ }
+
+ // output statistics
+ if (total_corr_count || uncorr_errors) {
+ printf("RS uncorrected errors:\n");
+ for (size_t i = 0; i < errors_per_index.size(); i++) {
+ int e = errors_per_index[i];
+ printf(" (%zu: %d)", i, e);
+ }
+ printf("\n");
+ }
+
+ return uncorr_errors ? -1 : total_corr_count;
+}
+
+
diff --git a/src/rsdecoder.hpp b/src/rsdecoder.hpp
new file mode 100644
index 0000000..ca97c91
--- /dev/null
+++ b/src/rsdecoder.hpp
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2015 Stefan Pöschel
+
+ Copyright (C) 2015 Matthias P. Braendli (http://www.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/>.
+ */
+
+#include <stdint.h>
+#include <vector>
+
+extern "C" {
+#include <fec.h>
+}
+
+class RSDecoder {
+ private:
+ void *rs_handle;
+ uint8_t rs_packet[120];
+ int corr_pos[10];
+ public:
+ RSDecoder();
+ ~RSDecoder();
+
+ /* Correct errors using reed-solomon decoder.
+ * Returns number of errors corrected, or -1 if some errors could not
+ * be corrected
+ */
+ int DecodeSuperframe(std::vector<uint8_t> &sf, int subch_index);
+};
+
+
diff --git a/src/tables.cpp b/src/tables.cpp
new file mode 100644
index 0000000..ed3ecbe
--- /dev/null
+++ b/src/tables.cpp
@@ -0,0 +1,311 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "tables.hpp"
+#include <string>
+#include <vector>
+#include <stdexcept>
+
+static const std::vector<const char*> language_names = {
+ "Unknown/not applicable",
+ "Albanian",
+ "Breton",
+ "Catalan",
+ "Croatian",
+ "Welsh",
+ "Czech",
+ "Danish",
+ "German",
+ "English",
+ "Spanish",
+ "Esperanto",
+ "Estonian",
+ "Basque",
+ "Faroese",
+ "French",
+ "Frisian",
+ "Irish",
+ "Gaelic",
+ "Galician",
+ "Icelandic",
+ "Italian",
+ "Lappish",
+ "Latin",
+ "Latvian",
+ "Luxembourgian",
+ "Lithuanian",
+ "Hungarian",
+ "Maltese",
+ "Dutch",
+ "Norwegian",
+ "Occitan",
+ "Polish",
+ "Portuguese",
+ "Romanian",
+ "Romansh",
+ "Serbian",
+ "Slovak",
+ "Slovene",
+ "Finnish",
+ "Swedish",
+ "Turkish",
+ "Flemish",
+ "Walloon",
+ "rfu",
+ "rfu",
+ "rfu",
+ "rfu",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Reserved for national assignment",
+ "Background sound/clean feed",
+ "rfu",
+ "rfu",
+ "rfu",
+ "rfu",
+ "Zulu",
+ "Vietnamese",
+ "Uzbek",
+ "Urdu",
+ "Ukranian",
+ "Thai",
+ "Telugu",
+ "Tatar",
+ "Tamil",
+ "Tadzhik",
+ "Swahili",
+ "Sranan Tongo",
+ "Somali",
+ "Sinhalese",
+ "Shona",
+ "Serbo-Croat",
+ "Rusyn",
+ "Russian",
+ "Quechua",
+ "Pushtu",
+ "Punjabi",
+ "Persian",
+ "Papiamento",
+ "Oriya",
+ "Nepali",
+ "Ndebele",
+ "Marathi",
+ "Moldavian",
+ "Malaysian",
+ "Malagasay",
+ "Macedonian",
+ "Laotian",
+ "Korean",
+ "Khmer",
+ "Kazakh",
+ "Kannada",
+ "Japanese",
+ "Indonesian",
+ "Hindi",
+ "Hebrew",
+ "Hausa",
+ "Gurani",
+ "Gujurati",
+ "Greek",
+ "Georgian",
+ "Fulani",
+ "Dari",
+ "Chuvash",
+ "Chinese",
+ "Burmese",
+ "Bulgarian",
+ "Bengali",
+ "Belorussian",
+ "Bambora",
+ "Azerbaijani",
+ "Assamese",
+ "Armenian",
+ "Arabic",
+ "Amharic",
+};
+
+const char* get_language_name(size_t language_code)
+{
+ if (language_code < language_names.size())
+ {
+ return language_names[language_code];
+ }
+
+ throw std::runtime_error("Invalid language_code!");
+}
+
+// fig 0/18 0/19 announcement types (ETSI TS 101 756 V1.6.1 (2014-05) table 14 & 15)
+const char *announcement_types_str[16] = {
+ "Alarm",
+ "Road Traffic flash",
+ "Transport flash",
+ "Warning/Service",
+ "News flash",
+ "Area weather flash",
+ "Event announcement",
+ "Special event",
+ "Programme Information",
+ "Sport report",
+ "Financial report",
+ "Reserved for future definition",
+ "Reserved for future definition",
+ "Reserved for future definition",
+ "Reserved for future definition",
+ "Reserved for future definition"
+};
+
+const char* get_announcement_type(size_t announcement_type)
+{
+ return announcement_types_str[announcement_type];
+}
+
+
+// fig 0/17 Programme type codes
+#define INTERNATIONAL_TABLE_SIZE 2
+#define PROGRAMME_TYPE_CODES_SIZE 32
+const char *Programme_type_codes_str[INTERNATIONAL_TABLE_SIZE][PROGRAMME_TYPE_CODES_SIZE] = {
+ { // ETSI TS 101 756 V1.6.1 (2014-05) table 12
+ "No programme type", "News",
+ "Current Affairs", "Information",
+ "Sport", "Education",
+ "Drama", "Culture",
+ "Science", "Varied",
+ "Pop Music", "Rock Music",
+ "Easy Listening Music", "Light Classical",
+ "Serious Classical", "Other Music",
+ "Weather/meteorology", "Finance/Business",
+ "Children's programmes", "Social Affairs",
+ "Religion", "Phone In",
+ "Travel", "Leisure",
+ "Jazz Music", "Country Music",
+ "National Music", "Oldies Music",
+ "Folk Music", "Documentary",
+ "Not used", "Not used"
+ },
+ { // ETSI TS 101 756 V1.6.1 (2014-05) table 13
+ "No program type", "News",
+ "Information", "Sports",
+ "Talk", "Rock",
+ "Classic Rock", "Adult Hits",
+ "Soft Rock", "Top 40",
+ "Country", "Oldies",
+ "Soft", "Nostalgia",
+ "Jazz", "Classical",
+ "Rhythm and Blues", "Soft Rhythm and Blues",
+ "Foreign Language", "Religious Music",
+ "Religious Talk", "Personality",
+ "Public", "College",
+ "rfu", "rfu",
+ "rfu", "rfu",
+ "rfu", "Weather",
+ "Not used", "Not used"
+ }
+};
+
+const char* get_programme_type(size_t int_table_Id, size_t pty)
+{
+ if ((int_table_Id - 1) < INTERNATIONAL_TABLE_SIZE) {
+ if (pty < PROGRAMME_TYPE_CODES_SIZE) {
+ return Programme_type_codes_str[int_table_Id - 1][pty];
+ }
+ else {
+ return "invalid programme type";
+ }
+ }
+ else {
+ return "unknown international table Id";
+ }
+}
+
+
+const char *DSCTy_types_str[64] = {
+ // ETSI TS 101 756 V1.6.1 (2014-05) table 2
+ "Unspecified data", "Traffic Message Channel (TMC)",
+ "Emergency Warning System (EWS)", "Interactive Text Transmission System (ITTS)",
+ "Paging", "Transparent Data Channel (TDC)",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "MPEG-2 Transport Stream, see ETSI TS 102 427", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Rfu",
+ "Rfu", "Embedded IP packets",
+ "Multimedia Object Transfer (MOT)", "Proprietary service: no DSCTy signalled",
+ "Not used", "Not used"
+};
+
+const char* get_dscty_type(size_t dscty)
+{
+ return DSCTy_types_str[dscty];
+}
+
+// ETSI TS 102 367 V1.2.1 (2006-01) 5.4.1 Conditional Access Mode (CAMode)
+// Used in FIG 0/3
+const char *CAMode_str[8] = {
+ "Sub-channel CA", "Data Group CA",
+ "MOT CA", "proprietary CA",
+ "reserved", "reserved",
+ "reserved", "reserved"
+};
+
+const char* get_ca_mode(size_t ca_mode)
+{
+ return CAMode_str[ca_mode];
+}
+
diff --git a/src/tables.hpp b/src/tables.hpp
new file mode 100644
index 0000000..992aabd
--- /dev/null
+++ b/src/tables.hpp
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#pragma once
+
+#include "utils.hpp"
+
+const char* get_language_name(size_t language_code);
+const char* get_announcement_type(size_t announcement_type);
+
+/* get_programme_type_str return the programme type string from international
+ * table Id and programme type
+ */
+const char* get_programme_type(size_t int_table_Id, size_t pty);
+
+// fig 0/2 fig 0/3 DSCTy types string:
+const char* get_dscty_type(size_t dscty);
+
+const char* get_ca_mode(size_t ca_mode);
+
diff --git a/src/utils.cpp b/src/utils.cpp
new file mode 100644
index 0000000..71df396
--- /dev/null
+++ b/src/utils.cpp
@@ -0,0 +1,154 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ 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/>.
+
+ etisnoop.cpp
+ Parse ETI NI G.703 file
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#include "utils.hpp"
+#include <cstring>
+
+using namespace std;
+
+static int verbosity = 0;
+
+void set_verbosity(int v)
+{
+ verbosity = v;
+}
+
+int get_verbosity()
+{
+ return verbosity;
+}
+
+void printbuf(string header,
+ int indent_level,
+ uint8_t* buffer,
+ size_t size,
+ string desc)
+{
+ if (verbosity > 0) {
+ for (int i = 0; i < indent_level; i++) {
+ printf("\t");
+ }
+
+ printf("%s", header.c_str());
+
+ if (verbosity > 1) {
+ if (size != 0) {
+ printf(": ");
+ }
+
+ for (size_t i = 0; i < size; i++) {
+ printf("%02x ", buffer[i]);
+ }
+ }
+
+ if (desc != "") {
+ printf(" [%s] ", desc.c_str());
+ }
+
+ printf("\n");
+ }
+}
+
+void printinfo(string header,
+ int indent_level,
+ int min_verb)
+{
+ if (verbosity >= min_verb) {
+ for (int i = 0; i < indent_level; i++) {
+ printf("\t");
+ }
+ printf("%s\n", header.c_str());
+ }
+}
+
+int sprintfMJD(char *dst, int mjd) {
+ // EN 62106 Annex G
+ // These formulas are applicable between the inclusive dates: 1st March 1900 to 28th February 2100
+ int y, m, k;
+ struct tm timeDate;
+
+ memset(&timeDate, 0, sizeof(struct tm));
+
+ // find Y, M, D from MJD
+ y = (int)(((double)mjd - 15078.2) / 365.25);
+ m = (int)(((double)mjd - 14956.1 - (int)((double)y * 365.25)) / 30.6001);
+ timeDate.tm_mday = mjd - 14956 - (int)((double)y * 365.25) - (int)((double)m * 30.6001);
+ if ((m == 14) || (m == 15)) {
+ k = 1;
+ }
+ else {
+ k = 0;
+ }
+ timeDate.tm_year = y + k;
+ timeDate.tm_mon = (m - 1 - (k * 12)) - 1;
+
+ // find WD from MJD
+ timeDate.tm_wday = (((mjd + 2) % 7) + 1) % 7;
+
+ //timeDate.tm_yday = 0; // Number of days since the first day of January not calculated
+ timeDate.tm_isdst = -1; // No time print then information not available
+
+ // print date string
+ if ((timeDate.tm_mday < 0) || (timeDate.tm_mon < 0) || (timeDate.tm_year < 0)) {
+ return sprintf(dst, "invalid MJD mday=%d mon=%d year=%d", timeDate.tm_mday, timeDate.tm_mon, timeDate.tm_year);
+ }
+ return strftime(dst, 256, "%a %b %d %Y", &timeDate);
+}
+
+char *strcatPNum(char *dest_str, uint16_t Programme_Number) {
+ uint8_t day, hour, minute;
+ char tempbuf[256];
+
+ minute = (uint8_t)(Programme_Number & 0x003F);
+ hour = (uint8_t)((Programme_Number >> 6) & 0x001F);
+ day = (uint8_t)((Programme_Number >> 11) & 0x001F);
+ if (day != 0) {
+ sprintf(tempbuf, "day of month=%d time=%02d:%02d", day, hour, minute);
+ }
+ else { // day == 0
+ // Special codes are allowed when the date part of the PNum field
+ // signals date = "0". In this case, the hours and minutes part of
+ // the field shall contain a special code, as follows
+ if ((hour == 0) && (minute == 0)) {
+ sprintf(tempbuf, "Status code: no meaningful PNum is currently provided");
+ }
+ else if ((hour == 0) && (minute == 1)) {
+ sprintf(tempbuf, "Blank code: the current programme is not worth recording");
+ }
+ else if ((hour == 0) && (minute == 2)) {
+ sprintf(tempbuf, "Interrupt code: the interrupt is unplanned (for example a traffic announcement)");
+ }
+ else {
+ sprintf(tempbuf, "invalid value");
+ }
+ }
+ return strcat(dest_str, tempbuf);
+}
+
+
diff --git a/src/utils.hpp b/src/utils.hpp
new file mode 100644
index 0000000..057447f
--- /dev/null
+++ b/src/utils.hpp
@@ -0,0 +1,57 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ 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/>.
+
+ etisnoop.cpp
+ Parse ETI NI G.703 file
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#pragma once
+
+#include <string>
+#include <cstdint>
+#include <cinttypes>
+
+void set_verbosity(int v);
+int get_verbosity(void);
+
+void printbuf(std::string header,
+ int indent_level,
+ uint8_t* buffer,
+ size_t size,
+ std::string desc="");
+
+void printinfo(std::string header,
+ int indent_level,
+ int min_verb);
+
+// sprintfMJD: convert MJD (Modified Julian Date) into date string
+int sprintfMJD(char *dst, int mjd);
+
+// strcatPNum decode Programme_Number into string and append it to dest_str
+// Programme_Number: this 16-bit field shall define the date and time at which
+// a programme begins or will be continued. This field is coded in the same way
+// as the RDS "Programme Item Number (PIN)" feature (EN 62106).
+char *strcatPNum(char *dest_str, uint16_t Programme_Number);
+
diff --git a/src/watermarkdecoder.hpp b/src/watermarkdecoder.hpp
new file mode 100644
index 0000000..57196e1
--- /dev/null
+++ b/src/watermarkdecoder.hpp
@@ -0,0 +1,98 @@
+/*
+ Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://www.csp.it/)
+ Copyright (C) 2016 Matthias P. Braendli (http://www.opendigitalradio.org)
+ Copyright (C) 2015 Data Path
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Authors:
+ Sergio Sagliocco <sergio.sagliocco@csp.it>
+ Matthias P. Braendli <matthias@mpb.li>
+ / | |- ')|) |-|_ _ (|,_ .| _ ,_ \
+ Data Path \(|(||_(|/_| (||_||(a)_||||(|||.(_()|||/
+
+*/
+
+#pragma once
+#include <vector>
+#include <sstream>
+#include "utils.hpp"
+
+class WatermarkDecoder
+{
+ public:
+ WatermarkDecoder() {}
+
+ void push_confind_bit(bool confind)
+ {
+ // The ConfInd of FIG 0/10 contains the CRC-DABMUX and ODR-DabMux watermark
+ m_confind_bits.push_back(confind);
+ }
+
+ std::string calculate_watermark()
+ {
+ // First try to find the 0x55 0x55 sync in the waternark data
+ size_t bit_ix;
+ int alternance_count = 0;
+ bool last_bit = 1;
+ for (bit_ix = 0; bit_ix < m_confind_bits.size(); bit_ix++) {
+ if (alternance_count == 16) {
+ break;
+ }
+ else {
+ if (last_bit != m_confind_bits[bit_ix]) {
+ last_bit = m_confind_bits[bit_ix];
+ alternance_count++;
+ }
+ else {
+ alternance_count = 0;
+ last_bit = 1;
+ }
+ }
+
+ }
+
+ printf("Found SYNC at offset %zu out of %zu\n", bit_ix - alternance_count, m_confind_bits.size());
+
+ std::stringstream watermark_ss;
+
+ uint8_t b = 0;
+ size_t i = 0;
+ while (bit_ix < m_confind_bits.size()) {
+
+ b |= m_confind_bits[bit_ix] << (7 - i);
+
+ if (i == 7) {
+ watermark_ss << (char)b;
+
+ b = 0;
+ i = 0;
+ }
+ else {
+ i++;
+ }
+
+ bit_ix += 2;
+ }
+
+ return watermark_ss.str();
+ }
+
+ private:
+ const WatermarkDecoder& operator=(const WatermarkDecoder&) = delete;
+ WatermarkDecoder(const WatermarkDecoder&) = delete;
+
+ std::vector<bool> m_confind_bits;
+};
+
diff --git a/src/wavfile.c b/src/wavfile.c
new file mode 100644
index 0000000..9ea231b
--- /dev/null
+++ b/src/wavfile.c
@@ -0,0 +1,82 @@
+/*
+A simple sound library for CSE 20211 by Douglas Thain
+For course assignments, you should not change this file.
+For complete documentation, see:
+http://www.nd.edu/~dthain/courses/cse20211/fall2013/wavfile
+*/
+
+#include "wavfile.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct wavfile_header {
+ char riff_tag[4];
+ int riff_length;
+ char wave_tag[4];
+ char fmt_tag[4];
+ int fmt_length;
+ short audio_format;
+ short num_channels;
+ int sample_rate;
+ int byte_rate;
+ short block_align;
+ short bits_per_sample;
+ char data_tag[4];
+ int data_length;
+};
+
+FILE * wavfile_open( const char *filename, int rate )
+{
+ struct wavfile_header header;
+
+ int samples_per_second = rate;
+ int bits_per_sample = 16;
+
+ strncpy(header.riff_tag,"RIFF",4);
+ strncpy(header.wave_tag,"WAVE",4);
+ strncpy(header.fmt_tag,"fmt ",4);
+ strncpy(header.data_tag,"data",4);
+
+ header.riff_length = 0;
+ header.fmt_length = 16;
+ header.audio_format = 1;
+ header.num_channels = 2;
+ header.sample_rate = samples_per_second;
+ header.byte_rate = samples_per_second*(bits_per_sample/8);
+ header.block_align = bits_per_sample/8;
+ header.bits_per_sample = bits_per_sample;
+ header.data_length = 0;
+
+ FILE * file = fopen(filename,"w+");
+ if(!file) return 0;
+
+ fwrite(&header,sizeof(header),1,file);
+
+ fflush(file);
+
+ return file;
+
+}
+
+void wavfile_write( FILE *file, short data[], int length )
+{
+ fwrite(data,sizeof(short),length,file);
+}
+
+void wavfile_close( FILE *file )
+{
+ int file_length = ftell(file);
+
+ int data_length = file_length - sizeof(struct wavfile_header);
+ fseek(file,sizeof(struct wavfile_header) - sizeof(int),SEEK_SET);
+ fwrite(&data_length,sizeof(data_length),1,file);
+
+ int riff_length = file_length - 8;
+ fseek(file,4,SEEK_SET);
+ fwrite(&riff_length,sizeof(riff_length),1,file);
+
+ fclose(file);
+}
+
diff --git a/src/wavfile.h b/src/wavfile.h
new file mode 100644
index 0000000..09e8371
--- /dev/null
+++ b/src/wavfile.h
@@ -0,0 +1,17 @@
+/*
+A simple sound library for CSE 20211 by Douglas Thain
+For course assignments, you should not change this file.
+For complete documentation, see:
+http://www.nd.edu/~dthain/courses/cse20211/fall2013/wavfile
+*/
+
+#ifndef WAVFILE_H
+#define WAVFILE_H
+
+#include <stdio.h>
+
+FILE * wavfile_open( const char *filename, int rate );
+void wavfile_write( FILE *file, short data[], int length );
+void wavfile_close( FILE * file );
+
+#endif