diff options
35 files changed, 3341 insertions, 2720 deletions
@@ -4,7 +4,9 @@ Required dependencies: * libfec from Phil Karn, with compatibility patch: [ka9q-fec](https://github.com/Opendigitalradio/ka9q-fec) * Boost 1.48 or later -* Optional ZeroMQ 4 from [http://www.zeromq.org](http://www.zeromq.org) +* Optional ZeroMQ 4 from [http://www.zeromq.org](http://www.zeromq.org). + Please prefer the zeromq from your distribution, but mind that some distributions + ship ZeroMQ 2, which is not enough. Use the --disable-output-zeromq ./configure option if you don't have ZeroMQ. @@ -21,17 +23,6 @@ Install libfec [as root] % make install # Install the library -Install zeromq 4.0.3 --------------------- - - % wget http://download.zeromq.org/zeromq-4.0.3.tar.gz - % tar -f zeromq-4.0.3.tar.gz -x - % cd zeromq-4.0.3 - % ./configure - % make - [as root] - % make install - Install odr-dabmux ------------------ diff --git a/configure.ac b/configure.ac index 20ddeaf..a24258b 100644 --- a/configure.ac +++ b/configure.ac @@ -52,10 +52,18 @@ AC_PROG_CC AM_PROG_CC_C_O AC_PROG_INSTALL +AX_CXX_COMPILE_STDCXX_11(noext,mandatory) + # Checks for libraries. AC_CHECK_LIB([pthread], [pthread_create], [], AC_MSG_ERROR([libpthread is required])) AX_BOOST_BASE([1.41.0], [], AC_MSG_ERROR([BOOST 1.41 or later is required])) +# we need to check for libm before checking for fec +AC_CHECK_LIB([m], [sin]) +AC_SEARCH_LIBS([init_rs_char], [fec], [], [ + AC_MSG_ERROR([unable to find libfec]) +]) + # Checks for header files. AC_MSG_CHECKING([for OS type]) AC_PREPROC_IFELSE( @@ -188,6 +196,11 @@ AS_IF([test "x$enable_output_zeromq" = "xyes"], AC_CHECK_LIB(zmq, zmq_init, [true], [AC_MSG_ERROR([ZeroMQ libzmq is required])])) AS_IF([test "x$enable_output_zeromq" = "xyes"], AC_DEFINE(HAVE_OUTPUT_ZEROMQ, [1], [Define if ZeroMQ output is enabled])) +# EDI +AC_ARG_ENABLE([output_edi], + AS_HELP_STRING([--enable-output-edi], [Enable EDI output])) +AS_IF([test "x$enable_output_edi" = "xyes"], + [AC_DEFINE(HAVE_OUTPUT_EDI, [1], [Define if EDI output is enabled])]) # Link against lzmq AM_CONDITIONAL([HAVE_ZEROMQ_TEST], @@ -281,7 +294,7 @@ echo echo "Outputs:" enabled="" disabled="" -for output in file fifo udp tcp raw simul zeromq +for output in file fifo udp tcp raw simul zeromq edi do eval var=\$enable_output_$output AS_IF([test "x$var" = "xyes"], diff --git a/doc/stats_dabmux_multi.py b/doc/stats_dabmux_multi.py index 7c21d84..a2587a4 100755 --- a/doc/stats_dabmux_multi.py +++ b/doc/stats_dabmux_multi.py @@ -5,11 +5,9 @@ import sys import json -import socket +import zmq import os -SOCK_RECV_SIZE = 10240 - config_top = """ """ @@ -72,6 +70,8 @@ right.warning -40:0 right.critical -80:0 """ +ctx = zmq.Context() + if not os.environ.get("MUNIN_CAP_MULTIGRAPH"): print("This needs munin version 1.4 at least") sys.exit(1) @@ -81,10 +81,11 @@ def connect(): returns: the socket""" - sock = socket.socket() - sock.connect(("localhost", 12720)) + sock = zmq.Socket(ctx, zmq.REQ) + sock.connect("tcp://localhost:12720") - version = json.loads(sock.recv(SOCK_RECV_SIZE)) + sock.send("info") + version = json.loads(sock.recv()) if not version['service'].startswith("ODR-DabMux"): sys.stderr.write("Wrong version\n") @@ -94,8 +95,8 @@ def connect(): if len(sys.argv) == 1: sock = connect() - sock.send("values\n") - values = json.loads(sock.recv(SOCK_RECV_SIZE))['values'] + sock.send("values") + values = json.loads(sock.recv())['values'] munin_values = "" for ident in values: @@ -115,9 +116,9 @@ if len(sys.argv) == 1: elif len(sys.argv) == 2 and sys.argv[1] == "config": sock = connect() - sock.send("config\n") + sock.send("config") - config = json.loads(sock.recv(SOCK_RECV_SIZE)) + config = json.loads(sock.recv()) munin_config = config_top @@ -125,4 +126,6 @@ elif len(sys.argv) == 2 and sys.argv[1] == "config": munin_config += config_template.format(ident=conf) print(munin_config) +else: + sys.exit(1) diff --git a/gui/muxconfig.py b/gui/muxconfig.py index 64321a2..4b307fd 100644 --- a/gui/muxconfig.py +++ b/gui/muxconfig.py @@ -20,7 +20,7 @@ # # You should have received a copy of the GNU General Public License # along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. -import socket +import zmq import json class General(object): @@ -95,23 +95,45 @@ class ConfigurationHandler(object): # local copy of the configuration self._server_version = None self._config = None + self._statistics = None + + self._ctx = zmq.Context() + self.sock = zmq.Socket(self._ctx, zmq.REQ) + self.sock.connect("tcp://{}:{}".format(self._host, self._port)) def load(self): """Load the configuration from the multiplexer and save it locally""" - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.send(b'info') + server_info = self.sock.recv() + + self.sock.send(b'getptree') + config_info = self.sock.recv() + + print("Config '%r'" % config_info) + + self._server_version = json.loads(server_info)['service'] + self._config = json.loads(config_info) - s.connect((self._host, self._port)) - s.sendall(b'getptree\n') - server_info = s.recv(32768) - config_info = s.recv(32768) - s.close() + def update_stats(self): + """Load the statistics from the multiplexer and + save them locally""" - self._server_version = json.loads(server_info.decode())['service'] - self._config = json.loads(config_info.decode()) + self.sock.send(b'info') + server_info = self.sock.recv() + + self.sock.send(b'values') + stats_info = self.sock.recv() + + self._statistics = json.loads(stats_info)['values'] def get_full_configuration(self): - return self._config + return json.dumps(self._config, indent=4) + + def set_full_configuration(self, config_json): + self.sock.send(b'setptree', flags=zmq.SNDMORE) + self.sock.send(config_json) + return self.sock.recv() == "OK" def get_mux_version(self): return self._server_version @@ -130,3 +152,8 @@ class ConfigurationHandler(object): def get_general_options(self): return General(self._config) + + def get_stats_dict(self): + """Return a dictionary with all stats""" + self.update_stats() + return self._statistics diff --git a/gui/odr-dabmux-gui.py b/gui/odr-dabmux-gui.py index 84331b5..caa4ea7 100755 --- a/gui/odr-dabmux-gui.py +++ b/gui/odr-dabmux-gui.py @@ -28,13 +28,46 @@ # along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. from muxconfig import * -from bottle import route, run, template, static_file +from bottle import route, run, template, static_file, request import json conf = ConfigurationHandler('localhost') @route('/config') def config(): + """Show the JSON ptree in a textbox for editing""" + + conf.load() + + return template('configeditor', + version = conf.get_mux_version(), + config = conf.get_full_configuration(), + message = "") + +@route('/config', method="POST") +def config_json_post(): + """Update the ODR-DabMux configuration with the JSON ptree + given as POST data""" + + new_config = request.forms.get('config') + print("New config %s" % new_config) + + success = conf.set_full_configuration(new_config) + + if success: + successmessage = "Success" + else: + successmessage = "Failure" + + conf.load() + + return template('configeditor', + version = conf.get_mux_version(), + config = conf.get_full_configuration(), + message = successmessage) + +@route('/config.json', method="GET") +def config_json_get(): """Return a application/json containing the full ptree of the mux""" @@ -43,6 +76,7 @@ def config(): return {'version': conf.get_mux_version(), 'config': conf.get_full_configuration()} + @route('/') def index(): conf.load() @@ -62,6 +96,17 @@ def index(): version = conf.get_mux_version(), services = conf.get_services()) +@route('/stats') +def index(): + conf.load() + + return template('stats', + version = conf.get_mux_version()) + +@route('/stats.json') +def stats_json(): + return conf.get_stats_dict() + @route('/static/<filename:path>') def send_static(filename): diff --git a/gui/static/stats.js b/gui/static/stats.js new file mode 100644 index 0000000..7b07099 --- /dev/null +++ b/gui/static/stats.js @@ -0,0 +1,17 @@ +var updatefunc = function(event) { + $('#statdata p').remove(); + $.getJSON('/stats.json', function(result) { + $.each(result, function(name) { + // TODO: use a hidden template inside the DOM instead + // of building the HTML here + $("<p></p>") + .append(result[name]['inputstat']['num_underruns']) + .appendTo('#statdata'); + }); + }); +} + +// Handle clicks on the to change visiblity of panes +setInterval(updatefunc, 1000); + + diff --git a/gui/views/configeditor.tpl b/gui/views/configeditor.tpl new file mode 100644 index 0000000..9d1d193 --- /dev/null +++ b/gui/views/configeditor.tpl @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <title>ODR-DabMux Configuration Editor</title> + <link rel="stylesheet" href="static/style.css" type="text/css" media="screen" charset="utf-8"/> + <script type="text/javascript" src="static/jquery-1.7.1.min.js"></script> +</head> +<body> + <h1>Configuration for {{version}}</h1> + + <p><a href="/config">Reload</a></p> + + % if message: + <p>{{message}}</p> + % end + + <form action="/config" method="post"> + <p> + <textarea name="config" cols="60" rows="50">{{config}}</textarea> + </p> + + <p> + <input type="submit" value="Update ODR-DabMux configuration"></input> + </p> + </form> + +</body> +</html> + diff --git a/gui/views/stats.tpl b/gui/views/stats.tpl new file mode 100644 index 0000000..30d82c5 --- /dev/null +++ b/gui/views/stats.tpl @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <title>ODR-DabMux Statistics</title> + <link rel="stylesheet" href="static/style.css" type="text/css" media="screen" charset="utf-8"/> + <script type="text/javascript" src="static/jquery-1.7.1.min.js"></script> +</head> +<body> + <h1>Subchannel stats for {{version}}</h1> + + <a id="update">Update</a> + + <div id="subchannels"> + <p>Subchannels</p> + <div id="statdata"></div> + </div> + <script type="text/javascript" src="static/stats.js"></script> +</body> +</html> + diff --git a/m4/ax_cxx_compile_stdcxx_11.m4 b/m4/ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 0000000..a9a8f58 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,165 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++11 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX11 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> +# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 10 + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template<typename T> + using member = typename T::member_type; + + template<typename T> + void func(...) {} + + template<typename T> + void func(member<T>*) {} + + void test(); + + void test() { + func<foo>(0); + } + } +]]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], + [$2], [optional], [ax_cxx_compile_cxx11_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=c++11 -std=c++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + AC_MSG_NOTICE([No compiler with C++11 support was found]) + else + HAVE_CXX11=1 + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++11 syntax]) + fi + + AC_SUBST(HAVE_CXX11) + fi +]) diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 443e26d..6092e66 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -125,15 +125,8 @@ int hexparse(std::string input) void parse_ptree(boost::property_tree::ptree& pt, - vector<dabOutput*> &outputs, - dabEnsemble* ensemble, - bool* enableTist, - unsigned* FICL, - bool* factumAnalyzer, - unsigned long* limit, - BaseRemoteController** rc, - int* mgmtserverport, - edi_configuration_t* edi + boost::shared_ptr<dabEnsemble> ensemble, + boost::shared_ptr<BaseRemoteController> rc ) { using boost::property_tree::ptree; @@ -150,38 +143,11 @@ void parse_ptree(boost::property_tree::ptree& pt, ensemble->mode = 0; } - if (ensemble->mode == 3) { - *FICL = 32; - } - else { - *FICL = 24; - } - - /* Number of frames to generate */ - *limit = pt_general.get("nbframes", 0); - /* Enable Logging to syslog conditionally */ if (pt_general.get<bool>("syslog", false)) { etiLog.register_backend(new LogToSyslog()); // TODO don't leak the LogToSyslog backend } - *factumAnalyzer = pt_general.get("writescca", false); - - *enableTist = pt_general.get("tist", false); - - *mgmtserverport = pt_general.get<int>("managementport", - pt_general.get<int>("statsserverport", 0) ); - - /************** READ REMOTE CONTROL PARAMETERS *************/ - ptree pt_rc = pt.get_child("remotecontrol"); - int telnetport = pt_rc.get<int>("telnetport", 0); - - if (telnetport != 0) { - *rc = new RemoteControllerTelnet(telnetport); - } - else { - *rc = new RemoteControllerDummy(); - } /******************** READ ENSEMBLE PARAMETERS *************/ ptree pt_ensemble = pt.get_child("ensemble"); @@ -254,19 +220,35 @@ void parse_ptree(boost::property_tree::ptree& pt, /******************** READ SERVICES PARAMETERS *************/ - map<string, DabService*> allservices; + map<string, shared_ptr<DabService> > allservices; /* For each service, we keep a separate SCIdS counter */ - map<DabService*, int> SCIdS_per_service; + map<shared_ptr<DabService>, int> SCIdS_per_service; ptree pt_services = pt.get_child("services"); for (ptree::iterator it = pt_services.begin(); it != pt_services.end(); ++it) { string serviceuid = it->first; ptree pt_service = it->second; - DabService* service = new DabService(serviceuid); - ensemble->services.push_back(service); - service->enrol_at(**rc); + + shared_ptr<DabService> service; + + bool service_already_existing = false; + + for (auto srv : ensemble->services) + { + if (srv->uid == serviceuid) { + service = srv; + service_already_existing = true; + break; + } + } + + if (not service_already_existing) { + auto new_srv = make_shared<DabService>(serviceuid); + ensemble->services.push_back(new_srv); + service = new_srv; + } int success = -5; @@ -332,13 +314,13 @@ void parse_ptree(boost::property_tree::ptree& pt, ptree pt_subchans = pt.get_child("subchannels"); for (ptree::iterator it = pt_subchans.begin(); it != pt_subchans.end(); ++it) { string subchanuid = it->first; - dabSubchannel* subchan = new dabSubchannel(); + dabSubchannel* subchan = new dabSubchannel(subchanuid); ensemble->subchannels.push_back(subchan); try { setup_subchannel_from_ptree(subchan, it->second, ensemble, - subchanuid, *rc); + subchanuid, rc); } catch (runtime_error &e) { etiLog.log(error, @@ -365,7 +347,7 @@ void parse_ptree(boost::property_tree::ptree& pt, string componentuid = it->first; ptree pt_comp = it->second; - DabService* service; + shared_ptr<DabService> service; try { // Those two uids serve as foreign keys to select the service+subchannel string service_uid = pt_comp.get<string>("service"); @@ -407,8 +389,6 @@ void parse_ptree(boost::property_tree::ptree& pt, DabComponent* component = new DabComponent(componentuid); - component->enrol_at(**rc); - component->serviceId = service->id; component->subchId = subchannel->id; component->SCIdS = SCIdS_per_service[service]++; @@ -495,65 +475,13 @@ void parse_ptree(boost::property_tree::ptree& pt, } - /******************** READ OUTPUT PARAMETERS ***************/ - map<string, dabOutput*> alloutputs; - ptree pt_outputs = pt.get_child("outputs"); - for (ptree::iterator it = pt_outputs.begin(); it != pt_outputs.end(); ++it) { - string outputuid = it->first; - - if (outputuid == "edi") { - ptree pt_edi = pt_outputs.get_child("edi"); - - edi->enabled = true; - - edi->dest_addr = pt_edi.get<string>("destination"); - edi->dest_port = pt_edi.get<unsigned int>("port"); - edi->source_port = pt_edi.get<unsigned int>("sourceport"); - - edi->dump = pt_edi.get<bool>("dump"); - edi->enable_pft = pt_edi.get<bool>("enable_pft"); - edi->verbose = pt_edi.get<bool>("verbose"); - } - else { - string uri = pt_outputs.get<string>(outputuid); - - size_t proto_pos = uri.find("://"); - if (proto_pos == std::string::npos) { - stringstream ss; - ss << "Output with uid " << outputuid << " no protocol defined!"; - throw runtime_error(ss.str()); - } - - char* uri_c = new char[512]; - memset(uri_c, 0, 512); - uri.copy(uri_c, 511); - - uri_c[proto_pos] = '\0'; - - char* outputName = uri_c + proto_pos + 3; - - dabOutput* output = new dabOutput(uri_c, outputName); - outputs.push_back(output); - - // keep outputs in map, and check for uniqueness of the uid - if (alloutputs.count(outputuid) == 0) { - alloutputs[outputuid] = output; - } - else { - stringstream ss; - ss << "output with uid " << outputuid << " not unique!"; - throw runtime_error(ss.str()); - } - } - } - } void setup_subchannel_from_ptree(dabSubchannel* subchan, boost::property_tree::ptree &pt, - dabEnsemble* ensemble, + boost::shared_ptr<dabEnsemble> ensemble, string subchanuid, - BaseRemoteController* rc) + boost::shared_ptr<BaseRemoteController> rc) { using boost::property_tree::ptree; using boost::property_tree::ptree_error; @@ -857,7 +785,7 @@ void setup_subchannel_from_ptree(dabSubchannel* subchan, if (nonblock) { switch (subchan->type) { #ifdef HAVE_FORMAT_PACKET - case 3: + case Packet: if (operations == dabInputPacketFileOperations) { operations = dabInputFifoOperations; #ifdef HAVE_FORMAT_EPM @@ -873,7 +801,7 @@ void setup_subchannel_from_ptree(dabSubchannel* subchan, break; #endif // defined(HAVE_FORMAT_PACKET) #ifdef HAVE_FORMAT_MPEG - case 0: + case Audio: if (operations == dabInputMpegFileOperations) { operations = dabInputMpegFifoOperations; } else if (operations == dabInputDabplusFileOperations) { @@ -886,6 +814,8 @@ void setup_subchannel_from_ptree(dabSubchannel* subchan, } break; #endif // defined(HAVE_FORMAT_MPEG) + case DataDmb: + case Fidc: default: stringstream ss; ss << "Subchannel with uid " << subchanuid << diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 4af010a..1eec783 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -36,23 +36,17 @@ #include "MuxElements.h" #include "DabMux.h" #include <boost/property_tree/ptree.hpp> +#include <boost/shared_ptr.hpp> void parse_ptree(boost::property_tree::ptree& pt, - std::vector<dabOutput*> &outputs, - dabEnsemble* ensemble, - bool* enableTist, - unsigned* FICL, - bool* factumAnalyzer, - unsigned long* limit, - BaseRemoteController** rc, - int* mgmtserverport, - edi_configuration_t* edi); + boost::shared_ptr<dabEnsemble> ensemble, + boost::shared_ptr<BaseRemoteController> rc); void setup_subchannel_from_ptree(dabSubchannel* subchan, boost::property_tree::ptree &pt, - dabEnsemble* ensemble, + boost::shared_ptr<dabEnsemble> ensemble, std::string subchanuid, - BaseRemoteController* rc); + boost::shared_ptr<BaseRemoteController> rc); #endif diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp new file mode 100644 index 0000000..4ca93aa --- /dev/null +++ b/src/DabMultiplexer.cpp @@ -0,0 +1,1843 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux 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-DabMux 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-DabMux. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "DabMultiplexer.h" +#include "ConfigParser.h" +#include <boost/make_shared.hpp> + +using namespace std; +using namespace boost; + +static unsigned char Padding_FIB[] = { + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +// Protection levels and bitrates for UEP. +const unsigned char ProtectionLevelTable[64] = { + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 2, 1, 0, + 4, 3, 1, + 4, 2, 0 +}; + +const unsigned short BitRateTable[64] = { + 32, 32, 32, 32, 32, + 48, 48, 48, 48, 48, + 56, 56, 56, 56, + 64, 64, 64, 64, 64, + 80, 80, 80, 80, 80, + 96, 96, 96, 96, 96, + 112, 112, 112, 112, + 128, 128, 128, 128, 128, + 160, 160, 160, 160, 160, + 192, 192, 192, 192, 192, + 224, 224, 224, 224, 224, + 256, 256, 256, 256, 256, + 320, 320, 320, + 384, 384, 384 +}; + +DabMultiplexer::DabMultiplexer( + boost::shared_ptr<BaseRemoteController> rc, + boost::property_tree::ptree pt) : + m_pt(pt), + m_rc(rc), + timestamp(0), + MNSC_increment_time(false), + m_watermarkSize(0), + m_watermarkPos(0), + sync(0x49C5F8), + currentFrame(0), + insertFIG(0), + rotateFIB(0) +{ + prepare_watermark(); + + ensemble = boost::make_shared<dabEnsemble>(); +} + +void DabMultiplexer::prepare_watermark() +{ + uint8_t buffer[sizeof(m_watermarkData) / 2]; + snprintf((char*)buffer, sizeof(buffer), + "%s %s, compiled at %s, %s", + PACKAGE_NAME, PACKAGE_VERSION, __DATE__, __TIME__); + + memset(m_watermarkData, 0, sizeof(m_watermarkData)); + m_watermarkData[0] = 0x55; // Sync + m_watermarkData[1] = 0x55; + m_watermarkSize = 16; + for (unsigned i = 0; i < strlen((char*)buffer); ++i) { + for (int j = 0; j < 8; ++j) { + uint8_t bit = (buffer[m_watermarkPos >> 3] >> (7 - (m_watermarkPos & 0x07))) & 1; + m_watermarkData[m_watermarkSize >> 3] |= bit << (7 - (m_watermarkSize & 0x07)); + ++m_watermarkSize; + bit = 1; + m_watermarkData[m_watermarkSize >> 3] |= bit << (7 - (m_watermarkSize & 0x07)); + ++m_watermarkSize; + ++m_watermarkPos; + } + } + m_watermarkPos = 0; +} + +void DabMultiplexer::update_config(boost::property_tree::ptree pt) +{ + ensemble_next = boost::make_shared<dabEnsemble>(); + + m_pt_next = pt; + + reconfigure(); +} + +void DabMultiplexer::reconfigure() +{ + parse_ptree(m_pt_next, ensemble_next, m_rc); +} + +void DabMultiplexer::set_edi_config(const edi_configuration_t& new_edi_conf) +{ + edi_conf = new_edi_conf; + +#if HAVE_OUTPUT_EDI + if (edi_conf.verbose) { + etiLog.log(info, "Setup EDI"); + } + + if (edi_conf.dump) { + edi_debug_file.open("./edi.debug"); + } + + if (edi_conf.enabled) { + edi_output.create(edi_conf.source_port); + } + + if (edi_conf.verbose) { + etiLog.log(info, "EDI set up"); + } + + // The TagPacket will then be placed into an AFPacket + AFPacketiser afPacketiser; + edi_afPacketiser = afPacketiser; + + // The AF Packet will be protected with reed-solomon and split in fragments + PFT pft(207, 3, edi_conf); + edi_pft = pft; +#endif +} + + +// Run a set of checks on the configuration +void DabMultiplexer::prepare() +{ + parse_ptree(m_pt, ensemble, m_rc); + + ensemble->enrol_at(m_rc); + + prepare_subchannels(); + prepare_services_components(); + prepare_data_inputs(); + + if (ensemble->subchannels.size() == 0) { + etiLog.log(error, "can't multiplex no subchannel!\n"); + throw MuxInitException(); + } + + vector<dabSubchannel*>::iterator subchannel = + ensemble->subchannels.end() - 1; + + if ((*subchannel)->startAddress + getSizeCu((*subchannel)) > 864) { + etiLog.log(error, "Total size in CU exceeds 864\n"); + printSubchannels(ensemble->subchannels); + throw MuxInitException(); + } + + /* These iterators are used to fill the respective FIG. + * It is necessary to cycle through all the FIGs that have + * to be transmitted because they do not fit in a single + * FIB. + * + * ETSI EN 300 799 Clauses 5.2 and 8.1 + */ + serviceProgFIG0_2 = ensemble->services.end(); + serviceDataFIG0_2 = ensemble->services.end(); + componentProgFIG0_8 = ensemble->components.end(); + componentDataFIG0_8 = ensemble->components.end(); + componentFIG0_13 = ensemble->components.end(); + transmitFIG0_13programme = false; + serviceFIG0_17 = ensemble->services.end(); + subchannelFIG0_1 = ensemble->subchannels.end(); + + + /* TODO: + * In a SFN, when reconfiguring the ensemble, the multiplexer + * has to be restarted (odr-dabmux doesn't support reconfiguration). + * Ideally, we must be able to restart transmission s.t. the receiver + * synchronisation is preserved. + */ + gettimeofday(&mnsc_time, NULL); +} + + +// Check and adjust subchannels +void DabMultiplexer::prepare_subchannels() +{ + set<unsigned char> ids; + + for (auto subchannel : ensemble->subchannels) { + if (ids.find(subchannel->id) != ids.end()) { + etiLog.log(error, + "Subchannel %u is set more than once!\n", + subchannel->id); + throw MuxInitException(); + } + ids.insert(subchannel->id); + } +} + +// Check and adjust services and components +void DabMultiplexer::prepare_services_components() +{ + set<uint32_t> ids; + dabProtection* protection = NULL; + + vector<DabComponent*>::iterator component; + vector<dabSubchannel*>::iterator subchannel; + + for (auto service : ensemble->services) { + if (ids.find(service->id) != ids.end()) { + etiLog.log(error, + "Service id 0x%x (%u) is set more than once!\n", + service->id, service->id); + throw MuxInitException(); + } + + // Get first component of this service + component = getComponent(ensemble->components, service->id); + if (component == ensemble->components.end()) { + etiLog.log(error, + "Service id 0x%x (%u) includes no component!\n", + service->id, service->id); + throw MuxInitException(); + } + + // Adjust service type from this first component + switch (service->getType(ensemble)) { + case 0: // Audio + service->program = true; + break; + case 1: + case 2: + case 3: + service->program = false; + break; + default: + etiLog.log(error, + "Error, unknown service type: %u\n", + service->getType(ensemble)); + throw MuxInitException(); + } + + service->enrol_at(m_rc); + + // Adjust components type for DAB+ + while (component != ensemble->components.end()) { + subchannel = + getSubchannel(ensemble->subchannels, (*component)->subchId); + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, "Error, service %u component " + "links to the invalid subchannel %u\n", + (*component)->serviceId, (*component)->subchId); + throw MuxInitException(); + } + + protection = &(*subchannel)->protection; + switch ((*subchannel)->type) { + case Audio: + { + if (protection->form == EEP) { + (*component)->type = 0x3f; // DAB+ + } + } + break; + case DataDmb: + case Fidc: + case Packet: + break; + default: + etiLog.log(error, + "Error, unknown subchannel type\n"); + throw MuxInitException(); + } + component = getComponent(ensemble->components, + service->id, component); + } + } + + // Init packet components SCId + int cur_packetid = 0; + for (auto component : ensemble->components) { + subchannel = getSubchannel(ensemble->subchannels, + component->subchId); + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + component->subchId, component->serviceId); + throw MuxInitException(); + } + if ((*subchannel)->type != Packet) continue; + + component->packet.id = cur_packetid++; + + component->enrol_at(m_rc); + + } + +} + +void DabMultiplexer::prepare_data_inputs() +{ + dabProtection* protection = NULL; + vector<dabSubchannel*>::iterator subchannel; + + // Prepare and check the data inputs + for (subchannel = ensemble->subchannels.begin(); + subchannel != ensemble->subchannels.end(); + ++subchannel) { + protection = &(*subchannel)->protection; + if (subchannel == ensemble->subchannels.begin()) { + (*subchannel)->startAddress = 0; + } else { + (*subchannel)->startAddress = (*(subchannel - 1))->startAddress + + getSizeCu(*(subchannel - 1)); + } + if ((*subchannel)->input->open((*subchannel)->inputUri) == -1) { + perror((*subchannel)->inputUri.c_str()); + throw MuxInitException(); + } + + // TODO Check errors + int subch_bitrate = (*subchannel)->input->setBitrate( (*subchannel)->bitrate); + if (subch_bitrate <= 0) { + etiLog.level(error) << "can't set bitrate for source " << + (*subchannel)->inputUri; + throw MuxInitException(); + } + (*subchannel)->bitrate = subch_bitrate; + + /* Use EEP unless we find a UEP configuration + * UEP is only used for MPEG audio, but some bitrates don't + * have a UEP profile (EN 300 401 Clause 6.2.1). + * For these bitrates, we must switch to EEP. + * + * AAC audio and data is already EEP + */ + if (protection->form == UEP) { + protection->form = EEP; + for (int i = 0; i < 64; i++) { + if ( (*subchannel)->bitrate == BitRateTable[i] && + protection->level == ProtectionLevelTable[i] ) { + protection->form = UEP; + protection->uep.tableIndex = i; + } + } + } + + /* EEP B can only be used for subchannels with bitrates + * multiple of 32kbit/s + */ + if ( protection->form == EEP && + protection->eep.profile == EEP_B && + subch_bitrate % 32 != 0 ) { + etiLog.level(error) << + "Cannot use EEP_B protection for subchannel " << + (*subchannel)->inputUri << + ": bitrate not multiple of 32kbit/s"; + throw MuxInitException(); + } + } +} + +/* Each call creates one ETI frame */ +void DabMultiplexer::mux_frame(std::vector<boost::shared_ptr<DabOutput> >& outputs) +{ + int cur; + time_t date; + unsigned char etiFrame[6144]; + unsigned short index = 0; + + vector<std::shared_ptr<DabService> >::iterator service; + vector<DabComponent*>::iterator component; + vector<dabSubchannel*>::iterator subchannel; + + // FIC Length, DAB Mode I, II, IV -> FICL = 24, DAB Mode III -> FICL = 32 + unsigned FICL = (ensemble->mode == 3 ? 32 : 24); + + // For EDI, save ETI(LI) Management data into a TAG Item DETI + TagDETI edi_tagDETI; + TagStarPTR edi_tagStarPtr; + list<TagESTn> edi_subchannels; + map<dabSubchannel*, TagESTn*> edi_subchannelToTag; + + // The above Tag Items will be assembled into a TAG Packet + TagPacket edi_tagpacket; + + edi_tagDETI.atstf = 0; // TODO add ATST support + + date = getDabTime(); + + // Initialise the ETI frame + memset(etiFrame, 0, 6144); + + /********************************************************************** + ********** Section SYNC of ETI(NI, G703) ************************* + **********************************************************************/ + + // See ETS 300 799 Clause 6 + eti_SYNC *etiSync = (eti_SYNC *) etiFrame; + + etiSync->ERR = edi_tagDETI.stat = 0xFF; // ETS 300 799, 5.2, no error + + //****** Field FSYNC *****// + // See ETS 300 799, 6.2.1.2 + sync ^= 0xffffff; + etiSync->FSYNC = sync; + + /********************************************************************** + *********** Section LIDATA of ETI(NI, G703) ********************** + **********************************************************************/ + + // See ETS 300 799 Figure 5 for a better overview of these fields. + + //****** Section FC ***************************************************/ + // 4 octets, starts at offset 4 + eti_FC *fc = (eti_FC *) &etiFrame[4]; + + //****** FCT ******// + // Incremente for each frame, overflows at 249 + fc->FCT = currentFrame % 250; + edi_tagDETI.dflc = currentFrame % 5000; + + //****** FICF ******// + // Fast Information Channel Flag, 1 bit, =1 if FIC present + fc->FICF = edi_tagDETI.ficf = 1; + + //****** NST ******// + /* Number of audio of data sub-channels, 7 bits, 0-64. + * In the 15-frame period immediately preceding a multiplex + * re-configuration, NST can take the value 0 (see annex E). + */ + fc->NST = ensemble->subchannels.size(); + + //****** FP ******// + /* Frame Phase, 3 bit counter, tells the COFDM generator + * when to insert the TII. Is also used by the MNSC. + */ + fc->FP = edi_tagDETI.fp = currentFrame & 0x7; + + //****** MID ******// + //Mode Identity, 2 bits, 01 ModeI, 10 modeII, 11 ModeIII, 00 ModeIV + fc->MID = edi_tagDETI.mid = ensemble->mode; //mode 2 needs 3 FIB, 3*32octets = 96octets + + //****** FL ******// + /* Frame Length, 11 bits, nb of words(4 bytes) in STC, EOH and MST + * if NST=0, FL=1+FICL words, FICL=24 or 32 depending on the mode. + * The FL is given in words (4 octets), see ETS 300 799 5.3.6 for details + */ + unsigned short FLtmp = 1 + FICL + (fc->NST); + + for (subchannel = ensemble->subchannels.begin(); + subchannel != ensemble->subchannels.end(); + ++subchannel) { + // Add STLsbch + FLtmp += getSizeWord(*subchannel); + } + + fc->setFrameLength(FLtmp); + index = 8; + + /******* Section STC **************************************************/ + // Stream Characterization, + // number of channels * 4 octets = nb octets total + int edi_stream_id = 1; + for (subchannel = ensemble->subchannels.begin(); + subchannel != ensemble->subchannels.end(); + ++subchannel) { + dabProtection* protection = &(*subchannel)->protection; + eti_STC *sstc = (eti_STC *) & etiFrame[index]; + + sstc->SCID = (*subchannel)->id; + sstc->startAddress_high = (*subchannel)->startAddress / 256; + sstc->startAddress_low = (*subchannel)->startAddress % 256; + // depends on the desired protection form + if (protection->form == UEP) { + sstc->TPL = 0x10 | + ProtectionLevelTable[protection->uep.tableIndex]; + } + else if (protection->form == EEP) { + sstc->TPL = 0x20 | (protection->eep.GetOption() << 2) | protection->level; + } + + // Sub-channel Stream Length, multiple of 64 bits + sstc->STL_high = getSizeDWord(*subchannel) / 256; + sstc->STL_low = getSizeDWord(*subchannel) % 256; + + TagESTn tag_ESTn(edi_stream_id++); + tag_ESTn.scid = (*subchannel)->id; + tag_ESTn.sad = (*subchannel)->startAddress; + tag_ESTn.tpl = sstc->TPL; + tag_ESTn.rfa = 0; // two bits + tag_ESTn.mst_length = getSizeByte(*subchannel) / 8; + assert(getSizeByte(*subchannel) % 8 == 0); + + edi_subchannels.push_back(tag_ESTn); + edi_subchannelToTag[*subchannel] = &edi_subchannels.back(); + index += 4; + } + + /******* Section EOH **************************************************/ + // End of Header 4 octets + eti_EOH *eoh = (eti_EOH *) & etiFrame[index]; + + //MNSC Multiplex Network Signalling Channel, 2 octets + + eoh->MNSC = 0; + + struct tm *time_tm = gmtime(&mnsc_time.tv_sec); + switch (fc->FP & 0x3) + { + case 0: + if (MNSC_increment_time) + { + MNSC_increment_time = false; + mnsc_time.tv_sec += 1; + } + { + + eti_MNSC_TIME_0 *mnsc = (eti_MNSC_TIME_0 *) &eoh->MNSC; + // Set fields according to ETS 300 799 -- 5.5.1 and A.2.2 + mnsc->type = 0; + mnsc->identifier = 0; + mnsc->rfa = 0; + } + break; + case 1: + { + eti_MNSC_TIME_1 *mnsc = (eti_MNSC_TIME_1 *) &eoh->MNSC; + mnsc->setFromTime(time_tm); + mnsc->accuracy = 1; + mnsc->sync_to_frame = 1; + } + break; + case 2: + { + eti_MNSC_TIME_2 *mnsc = (eti_MNSC_TIME_2 *) &eoh->MNSC; + mnsc->setFromTime(time_tm); + } + break; + case 3: + { + eti_MNSC_TIME_3 *mnsc = (eti_MNSC_TIME_3 *) &eoh->MNSC; + mnsc->setFromTime(time_tm); + } + break; + } + + edi_tagDETI.mnsc = eoh->MNSC; + + // CRC Cyclic Redundancy Checksum of the FC, STC and MNSC, 2 octets + unsigned short nbBytesCRC = 4 + ((fc->NST) * 4) + 2; + + unsigned short CRCtmp = 0xFFFF; + CRCtmp = crc16(CRCtmp, &etiFrame[4], nbBytesCRC); + CRCtmp ^= 0xffff; + eoh->CRC = htons(CRCtmp); + + /******* Section MST **************************************************/ + // Main Stream Data, if FICF=1 the first 96 or 128 bytes carry the FIC + // (depending on mode) + index = ((fc->NST) + 2 + 1) * 4; + edi_tagDETI.fic_data = &etiFrame[index]; + edi_tagDETI.fic_length = FICL * 4; + + // FIC Insertion + FIGtype0* fig0; + FIGtype0_0 *fig0_0; + FIGtype0_1 *figtype0_1; + + FIG_01_SubChannel_ShortF *fig0_1subchShort; + FIG_01_SubChannel_LongF *fig0_1subchLong1; + + FIGtype0_2 *fig0_2; + + FIGtype0_2_Service *fig0_2serviceAudio; + FIGtype0_2_Service_data *fig0_2serviceData; + FIGtype0_2_audio_component* audio_description; + FIGtype0_2_data_component* data_description; + FIGtype0_2_packet_component* packet_description; + + FIGtype0_3_header *fig0_3_header; + FIGtype0_3_data *fig0_3_data; + FIGtype0_9 *fig0_9; + FIGtype0_10 *fig0_10; + FIGtype0_17_programme *programme; + + FIGtype1_0 *fig1_0; + FIGtype1_1 *fig1_1; + FIGtype1_5 *fig1_5; + + tm* timeData; + + unsigned char figSize = 0; + + // FIB 0 Insertion + switch (insertFIG) { + + case 0: + case 4: + case 8: + case 12: + // FIG type 0/0, Multiplex Configuration Info (MCI), + // Ensemble information + fig0_0 = (FIGtype0_0 *) & etiFrame[index]; + + fig0_0->FIGtypeNumber = 0; + fig0_0->Length = 5; + fig0_0->CN = 0; + fig0_0->OE = 0; + fig0_0->PD = 0; + fig0_0->Extension = 0; + + fig0_0->EId = htons(ensemble->id); + fig0_0->Change = 0; + fig0_0->Al = 0; + fig0_0->CIFcnt_hight = (currentFrame / 250) % 20; + fig0_0->CIFcnt_low = (currentFrame % 250); + index = index + 6; + figSize += 6; + + break; + + case 1: + case 6: + case 10: + case 13: + // FIG type 0/1, MIC, Sub-Channel Organization, + // one instance of the part for each subchannel + figtype0_1 = (FIGtype0_1 *) & etiFrame[index]; + + figtype0_1->FIGtypeNumber = 0; + figtype0_1->Length = 1; + figtype0_1->CN = 0; + figtype0_1->OE = 0; + figtype0_1->PD = 0; + figtype0_1->Extension = 1; + index = index + 2; + figSize += 2; + + // Rotate through the subchannels until there is no more + // space in the FIG0/1 + if (subchannelFIG0_1 == ensemble->subchannels.end()) { + subchannelFIG0_1 = ensemble->subchannels.begin(); + } + + for (; subchannelFIG0_1 != ensemble->subchannels.end(); + ++subchannelFIG0_1) { + dabProtection* protection = &(*subchannelFIG0_1)->protection; + + if ( (protection->form == UEP && figSize > 27) || + (protection->form == EEP && figSize > 26) ) { + break; + } + + if (protection->form == UEP) { + fig0_1subchShort = + (FIG_01_SubChannel_ShortF*) &etiFrame[index]; + fig0_1subchShort->SubChId = (*subchannelFIG0_1)->id; + + fig0_1subchShort->StartAdress_high = + (*subchannelFIG0_1)->startAddress / 256; + fig0_1subchShort->StartAdress_low = + (*subchannelFIG0_1)->startAddress % 256; + + fig0_1subchShort->Short_Long_form = 0; + fig0_1subchShort->TableSwitch = 0; + fig0_1subchShort->TableIndex = + protection->uep.tableIndex; + + index = index + 3; + figSize += 3; + figtype0_1->Length += 3; + } + else if (protection->form == EEP) { + fig0_1subchLong1 = + (FIG_01_SubChannel_LongF*) &etiFrame[index]; + fig0_1subchLong1->SubChId = (*subchannelFIG0_1)->id; + + fig0_1subchLong1->StartAdress_high = + (*subchannelFIG0_1)->startAddress / 256; + fig0_1subchLong1->StartAdress_low = + (*subchannelFIG0_1)->startAddress % 256; + + fig0_1subchLong1->Short_Long_form = 1; + fig0_1subchLong1->Option = protection->eep.GetOption(); + fig0_1subchLong1->ProtectionLevel = + protection->level; + + fig0_1subchLong1->Sub_ChannelSize_high = + getSizeCu(*subchannelFIG0_1) / 256; + fig0_1subchLong1->Sub_ChannelSize_low = + getSizeCu(*subchannelFIG0_1) % 256; + + index = index + 4; + figSize += 4; + figtype0_1->Length += 4; + } + } + break; + + case 2: + case 9: + case 11: + case 14: + // FIG type 0/2, MCI, Service Organization, one instance of + // FIGtype0_2_Service for each subchannel + fig0_2 = NULL; + cur = 0; + + // Rotate through the subchannels until there is no more + // space in the FIG0/1 + if (serviceProgFIG0_2 == ensemble->services.end()) { + serviceProgFIG0_2 = ensemble->services.begin(); + } + + for (; serviceProgFIG0_2 != ensemble->services.end(); + ++serviceProgFIG0_2) { + if (!(*serviceProgFIG0_2)->nbComponent(ensemble->components)) { + continue; + } + + if ((*serviceProgFIG0_2)->getType(ensemble) != 0) { + continue; + } + + ++cur; + + if (fig0_2 == NULL) { + fig0_2 = (FIGtype0_2 *) & etiFrame[index]; + + fig0_2->FIGtypeNumber = 0; + fig0_2->Length = 1; + fig0_2->CN = 0; + fig0_2->OE = 0; + fig0_2->PD = 0; + fig0_2->Extension = 2; + index = index + 2; + figSize += 2; + } + + if (figSize + 3 + + (*serviceProgFIG0_2)->nbComponent(ensemble->components) + * 2 > 30) { + break; + } + + fig0_2serviceAudio = (FIGtype0_2_Service*) &etiFrame[index]; + + fig0_2serviceAudio->SId = htons((*serviceProgFIG0_2)->id); + fig0_2serviceAudio->Local_flag = 0; + fig0_2serviceAudio->CAId = 0; + fig0_2serviceAudio->NbServiceComp = + (*serviceProgFIG0_2)->nbComponent(ensemble->components); + index += 3; + fig0_2->Length += 3; + figSize += 3; + + int curCpnt = 0; + for (component = getComponent(ensemble->components, + (*serviceProgFIG0_2)->id); + component != ensemble->components.end(); + component = getComponent(ensemble->components, + (*serviceProgFIG0_2)->id, component)) { + subchannel = getSubchannel(ensemble->subchannels, + (*component)->subchId); + + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + (*component)->subchId, (*component)->serviceId); + throw MuxInitException(); + } + + switch ((*subchannel)->type) { + case Audio: + audio_description = + (FIGtype0_2_audio_component*)&etiFrame[index]; + audio_description->TMid = 0; + audio_description->ASCTy = (*component)->type; + audio_description->SubChId = (*subchannel)->id; + audio_description->PS = ((curCpnt == 0) ? 1 : 0); + audio_description->CA_flag = 0; + break; + case DataDmb: + data_description = + (FIGtype0_2_data_component*)&etiFrame[index]; + data_description->TMid = 1; + data_description->DSCTy = (*component)->type; + data_description->SubChId = (*subchannel)->id; + data_description->PS = ((curCpnt == 0) ? 1 : 0); + data_description->CA_flag = 0; + break; + case Packet: + packet_description = + (FIGtype0_2_packet_component*)&etiFrame[index]; + packet_description->TMid = 3; + packet_description->setSCId((*component)->packet.id); + packet_description->PS = ((curCpnt == 0) ? 1 : 0); + packet_description->CA_flag = 0; + break; + default: + etiLog.log(error, + "Component type not supported\n"); + throw MuxInitException(); + } + index += 2; + fig0_2->Length += 2; + figSize += 2; + if (figSize > 30) { + etiLog.log(error, + "Sorry, no space left in FIG 0/2 to insert " + "component %i of program service %i.\n", + curCpnt, cur); + throw MuxInitException(); + } + ++curCpnt; + } + } + break; + + case 3: + fig0_2 = NULL; + cur = 0; + + if (serviceDataFIG0_2 == ensemble->services.end()) { + serviceDataFIG0_2 = ensemble->services.begin(); + } + for (; serviceDataFIG0_2 != ensemble->services.end(); + ++serviceDataFIG0_2) { + if (!(*serviceDataFIG0_2)->nbComponent(ensemble->components)) { + continue; + } + + unsigned char type = (*serviceDataFIG0_2)->getType(ensemble); + if ((type == 0) || (type == 2)) { + continue; + } + + ++cur; + + if (fig0_2 == NULL) { + fig0_2 = (FIGtype0_2 *) & etiFrame[index]; + + fig0_2->FIGtypeNumber = 0; + fig0_2->Length = 1; + fig0_2->CN = 0; + fig0_2->OE = 0; + fig0_2->PD = 1; + fig0_2->Extension = 2; + index = index + 2; + figSize += 2; + } + + if (figSize + 5 + + (*serviceDataFIG0_2)->nbComponent(ensemble->components) + * 2 > 30) { + break; + } + + fig0_2serviceData = + (FIGtype0_2_Service_data*) &etiFrame[index]; + + fig0_2serviceData->SId = htonl((*serviceDataFIG0_2)->id); + fig0_2serviceData->Local_flag = 0; + fig0_2serviceData->CAId = 0; + fig0_2serviceData->NbServiceComp = + (*serviceDataFIG0_2)->nbComponent(ensemble->components); + fig0_2->Length += 5; + index += 5; + figSize += 5; + + int curCpnt = 0; + for (component = getComponent(ensemble->components, + (*serviceDataFIG0_2)->id); + component != ensemble->components.end(); + component = getComponent(ensemble->components, + (*serviceDataFIG0_2)->id, component)) { + subchannel = getSubchannel(ensemble->subchannels, + (*component)->subchId); + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + (*component)->subchId, (*component)->serviceId); + throw MuxInitException(); + } + + switch ((*subchannel)->type) { + case Audio: + audio_description = + (FIGtype0_2_audio_component*)&etiFrame[index]; + audio_description->TMid = 0; + audio_description->ASCTy = (*component)->type; + audio_description->SubChId = (*subchannel)->id; + audio_description->PS = ((curCpnt == 0) ? 1 : 0); + audio_description->CA_flag = 0; + break; + case DataDmb: + data_description = + (FIGtype0_2_data_component*)&etiFrame[index]; + data_description->TMid = 1; + data_description->DSCTy = (*component)->type; + data_description->SubChId = (*subchannel)->id; + data_description->PS = ((curCpnt == 0) ? 1 : 0); + data_description->CA_flag = 0; + break; + case Packet: + packet_description = + (FIGtype0_2_packet_component*)&etiFrame[index]; + packet_description->TMid = 3; + packet_description->setSCId((*component)->packet.id); + packet_description->PS = ((curCpnt == 0) ? 1 : 0); + packet_description->CA_flag = 0; + break; + default: + etiLog.log(error, + "Component type not supported\n"); + throw MuxInitException(); + } + index += 2; + fig0_2->Length += 2; + figSize += 2; + if (figSize > 30) { + etiLog.log(error, + "Sorry, no place left in FIG 0/2 to insert " + "component %i of data service %i.\n", + curCpnt, cur); + throw MuxInitException(); + } + ++curCpnt; + } + } + break; + + case 5: + fig0_3_header = NULL; + + for (component = ensemble->components.begin(); + component != ensemble->components.end(); + ++component) { + subchannel = getSubchannel(ensemble->subchannels, + (*component)->subchId); + + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + (*component)->subchId, (*component)->serviceId); + throw MuxInitException(); + } + + if ((*subchannel)->type != Packet) + continue; + + if (fig0_3_header == NULL) { + fig0_3_header = (FIGtype0_3_header*)&etiFrame[index]; + fig0_3_header->FIGtypeNumber = 0; + fig0_3_header->Length = 1; + fig0_3_header->CN = 0; + fig0_3_header->OE = 0; + fig0_3_header->PD = 0; + fig0_3_header->Extension = 3; + + index += 2; + figSize += 2; + } + + bool factumAnalyzer = m_pt.get("general.writescca", false); + + /* Warning: When bit SCCA_flag is unset(0), the multiplexer + * R&S does not send the SCCA field. But, in the Factum ETI + * analyzer, if this field is not there, it is an error. + */ + fig0_3_data = (FIGtype0_3_data*)&etiFrame[index]; + fig0_3_data->setSCId((*component)->packet.id); + fig0_3_data->rfa = 0; + fig0_3_data->SCCA_flag = 0; + // if 0, datagroups are used + fig0_3_data->DG_flag = !(*component)->packet.datagroup; + fig0_3_data->rfu = 0; + fig0_3_data->DSCTy = (*component)->type; + fig0_3_data->SubChId = (*subchannel)->id; + fig0_3_data->setPacketAddress((*component)->packet.address); + if (factumAnalyzer) { + fig0_3_data->SCCA = 0; + } + + fig0_3_header->Length += 5; + index += 5; + figSize += 5; + if (factumAnalyzer) { + fig0_3_header->Length += 2; + index += 2; + figSize += 2; + } + + if (figSize > 30) { + etiLog.log(error, + "can't add to Fic Fig 0/3, " + "too much packet service\n"); + throw MuxInitException(); + } + } + break; + + case 7: + fig0 = NULL; + if (serviceFIG0_17 == ensemble->services.end()) { + serviceFIG0_17 = ensemble->services.begin(); + } + for (; serviceFIG0_17 != ensemble->services.end(); + ++serviceFIG0_17) { + + if ( (*serviceFIG0_17)->pty == 0 && + (*serviceFIG0_17)->language == 0) { + continue; + } + + if (fig0 == NULL) { + fig0 = (FIGtype0*)&etiFrame[index]; + fig0->FIGtypeNumber = 0; + fig0->Length = 1; + fig0->CN = 0; + fig0->OE = 0; + fig0->PD = 0; + fig0->Extension = 17; + index += 2; + figSize += 2; + } + + if ((*serviceFIG0_17)->language == 0) { + if (figSize + 4 > 30) { + break; + } + } + else { + if (figSize + 5 > 30) { + break; + } + } + + programme = + (FIGtype0_17_programme*)&etiFrame[index]; + programme->SId = htons((*serviceFIG0_17)->id); + programme->SD = 1; + programme->PS = 0; + programme->L = (*serviceFIG0_17)->language != 0; + programme->CC = 0; + programme->Rfa = 0; + programme->NFC = 0; + if ((*serviceFIG0_17)->language == 0) { + etiFrame[index + 3] = (*serviceFIG0_17)->pty; + fig0->Length += 4; + index += 4; + figSize += 4; + } + else { + etiFrame[index + 3] = (*serviceFIG0_17)->language; + etiFrame[index + 4] = (*serviceFIG0_17)->pty; + fig0->Length += 5; + index += 5; + figSize += 5; + } + } + break; + } + + if (figSize > 30) { + etiLog.log(error, + "FIG too big (%i > 30)\n", figSize); + throw MuxInitException(); + } + + memcpy(&etiFrame[index], Padding_FIB, 30 - figSize); + index += 30 - figSize; + + CRCtmp = 0xffff; + CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); + CRCtmp ^= 0xffff; + etiFrame[index++] = ((char *) &CRCtmp)[1]; + etiFrame[index++] = ((char *) &CRCtmp)[0]; + + figSize = 0; + // FIB 1 insertion + switch (rotateFIB) { + case 0: // FIG 0/8 program + fig0 = NULL; + + if (componentProgFIG0_8 == ensemble->components.end()) { + componentProgFIG0_8 = ensemble->components.begin(); + } + for (; componentProgFIG0_8 != ensemble->components.end(); + ++componentProgFIG0_8) { + service = getService(*componentProgFIG0_8, + ensemble->services); + subchannel = getSubchannel(ensemble->subchannels, + (*componentProgFIG0_8)->subchId); + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + (*componentProgFIG0_8)->subchId, + (*componentProgFIG0_8)->serviceId); + throw MuxInitException(); + } + + if (!(*service)->program) + continue; + + if (fig0 == NULL) { + fig0 = (FIGtype0*)&etiFrame[index]; + fig0->FIGtypeNumber = 0; + fig0->Length = 1; + fig0->CN = 0; + fig0->OE = 0; + fig0->PD = 0; + fig0->Extension = 8; + index += 2; + figSize += 2; + } + + if ((*subchannel)->type == Packet) { // Data packet + if (figSize > 30 - 5) { + break; + } + etiFrame[index] = + ((*componentProgFIG0_8)->serviceId >> 8) & 0xFF; + etiFrame[index+1] = + ((*componentProgFIG0_8)->serviceId) & 0xFF; + fig0->Length += 2; + index += 2; + figSize += 2; + + FIGtype0_8_long* definition = + (FIGtype0_8_long*)&etiFrame[index]; + memset(definition, 0, 3); + definition->ext = 0; // no rfa + definition->SCIdS = (*componentProgFIG0_8)->SCIdS; + definition->LS = 1; + definition->setSCId((*componentProgFIG0_8)->packet.id); + fig0->Length += 3; + index += 3; // 8 minus rfa + figSize += 3; + } + else { // Audio, data stream or FIDC + if (figSize > 30 - 4) { + break; + } + etiFrame[index] = + ((*componentProgFIG0_8)->serviceId >> 8) & 0xFF; + etiFrame[index+1] = + ((*componentProgFIG0_8)->serviceId) & 0xFF; + + fig0->Length += 2; + index += 2; + figSize += 2; + + FIGtype0_8_short* definition = + (FIGtype0_8_short*)&etiFrame[index]; + memset(definition, 0, 2); + definition->ext = 0; // no rfa + definition->SCIdS = (*componentProgFIG0_8)->SCIdS; + definition->LS = 0; + definition->MscFic = 0; + definition->Id = (*componentProgFIG0_8)->subchId; + fig0->Length += 2; + index += 2; // 4 minus rfa + figSize += 2; + } + } + break; + + case 1: // FIG 0/8 data + fig0 = NULL; + + if (componentDataFIG0_8 == ensemble->components.end()) { + componentDataFIG0_8 = ensemble->components.begin(); + } + for (; componentDataFIG0_8 != ensemble->components.end(); + ++componentDataFIG0_8) { + service = getService(*componentDataFIG0_8, + ensemble->services); + + subchannel = getSubchannel(ensemble->subchannels, + (*componentDataFIG0_8)->subchId); + + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + (*componentDataFIG0_8)->subchId, + (*componentDataFIG0_8)->serviceId); + throw MuxInitException(); + } + + if ((*service)->program) + continue; + + if (fig0 == NULL) { + fig0 = (FIGtype0*)&etiFrame[index]; + fig0->FIGtypeNumber = 0; + fig0->Length = 1; + fig0->CN = 0; + fig0->OE = 0; + fig0->PD = 1; + fig0->Extension = 8; + index += 2; + figSize += 2; + } + + if ((*subchannel)->type == Packet) { // Data packet + if (figSize > 30 - 7) { + break; + } + etiFrame[index] = + ((*componentDataFIG0_8)->serviceId >> 8) & 0xFF; + etiFrame[index+1] = + ((*componentDataFIG0_8)->serviceId) & 0xFF; + fig0->Length += 4; + index += 4; + figSize += 4; + + FIGtype0_8_long* definition = + (FIGtype0_8_long*)&etiFrame[index]; + memset(definition, 0, 3); + definition->ext = 0; // no rfa + definition->SCIdS = (*componentDataFIG0_8)->SCIdS; + definition->LS = 1; + definition->setSCId((*componentDataFIG0_8)->packet.id); + fig0->Length += 3; + index += 3; // 8 minus rfa + figSize += 3; + } + else { // Audio, data stream or FIDC + if (figSize > 30 - 6) { + break; + } + etiFrame[index] = + ((*componentDataFIG0_8)->serviceId >> 8) & 0xFF; + etiFrame[index+1] = + ((*componentDataFIG0_8)->serviceId) & 0xFF; + fig0->Length += 4; + index += 4; + figSize += 4; + + FIGtype0_8_short* definition = + (FIGtype0_8_short*)&etiFrame[index]; + memset(definition, 0, 2); + definition->ext = 0; // no rfa + definition->SCIdS = (*componentDataFIG0_8)->SCIdS; + definition->LS = 0; + definition->MscFic = 0; + definition->Id = (*componentDataFIG0_8)->subchId; + fig0->Length += 2; + index += 2; // 4 minus rfa + figSize += 2; + } + } + break; + + case 3: + // FIG type 1/0, Service Information (SI), Ensemble Label + fig1_0 = (FIGtype1_0 *) & etiFrame[index]; + + fig1_0->Length = 21; + fig1_0->FIGtypeNumber = 1; + fig1_0->Extension = 0; + fig1_0->OE = 0; + fig1_0->Charset = 0; + fig1_0->EId = htons(ensemble->id); + index = index + 4; + + ensemble->label.writeLabel(&etiFrame[index]); + index = index + 16; + + etiFrame[index++] = ensemble->label.flag() >> 8; + etiFrame[index++] = ensemble->label.flag() & 0xFF; + + figSize += 22; + break; + + case 5: + case 6: + // FIG 0 / 13 + fig0 = NULL; + + if (componentFIG0_13 == ensemble->components.end()) { + componentFIG0_13 = ensemble->components.begin(); + + transmitFIG0_13programme = !transmitFIG0_13programme; + // Alternate between data and and programme FIG0/13, + // do not mix fig0 with PD=0 with extension 13 stuff + // that actually needs PD=1, and vice versa + } + + for (; componentFIG0_13 != ensemble->components.end(); + ++componentFIG0_13) { + + subchannel = getSubchannel(ensemble->subchannels, + (*componentFIG0_13)->subchId); + + if (subchannel == ensemble->subchannels.end()) { + etiLog.log(error, + "Subchannel %i does not exist for component " + "of service %i\n", + (*componentFIG0_13)->subchId, + (*componentFIG0_13)->serviceId); + throw MuxInitException(); + } + + if ( transmitFIG0_13programme && + (*subchannel)->type == Audio && + (*componentFIG0_13)->audio.uaType != 0xffff) { + if (fig0 == NULL) { + fig0 = (FIGtype0*)&etiFrame[index]; + fig0->FIGtypeNumber = 0; + fig0->Length = 1; + fig0->CN = 0; + fig0->OE = 0; + fig0->PD = 0; + fig0->Extension = 13; + index += 2; + figSize += 2; + } + + if (figSize > 30 - (3+4+11)) { + break; + } + + FIG0_13_shortAppInfo* info = + (FIG0_13_shortAppInfo*)&etiFrame[index]; + info->SId = htonl((*componentFIG0_13)->serviceId) >> 16; + info->SCIdS = (*componentFIG0_13)->SCIdS; + info->No = 1; + index += 3; + figSize += 3; + fig0->Length += 3; + + FIG0_13_app* app = (FIG0_13_app*)&etiFrame[index]; + app->setType((*componentFIG0_13)->audio.uaType); + app->length = 4; + app->xpad = htonl(0x0cbc0000); + /* xpad meaning + CA = 0 + CAOrg = 0 + Rfu = 0 + AppTy(5) = 12 (MOT, start of X-PAD data group) + DG = 0 (MSC data groups used) + Rfu = 0 + DSCTy(6) = 60 (MOT) + CAOrg(16) = 0 + */ + + index += 2 + app->length; + figSize += 2 + app->length; + fig0->Length += 2 + app->length; + } + else if (!transmitFIG0_13programme && + (*subchannel)->type == Packet && + (*componentFIG0_13)->packet.appType != 0xffff) { + + if (fig0 == NULL) { + fig0 = (FIGtype0*)&etiFrame[index]; + fig0->FIGtypeNumber = 0; + fig0->Length = 1; + fig0->CN = 0; + fig0->OE = 0; + fig0->PD = 1; + fig0->Extension = 13; + index += 2; + figSize += 2; + } + + if (figSize > 30 - (5+2)) { + break; + } + + FIG0_13_longAppInfo* info = + (FIG0_13_longAppInfo*)&etiFrame[index]; + info->SId = htonl((*componentFIG0_13)->serviceId); + info->SCIdS = (*componentFIG0_13)->SCIdS; + info->No = 1; + index += 5; + figSize += 5; + fig0->Length += 5; + + FIG0_13_app* app = (FIG0_13_app*)&etiFrame[index]; + app->setType((*componentFIG0_13)->packet.appType); + app->length = 0; + index += 2; + figSize += 2; + fig0->Length += 2; + } + } + break; + + case 7: + //Time and country identifier + fig0_10 = (FIGtype0_10 *) & etiFrame[index]; + + fig0_10->FIGtypeNumber = 0; + fig0_10->Length = 5; + fig0_10->CN = 0; + fig0_10->OE = 0; + fig0_10->PD = 0; + fig0_10->Extension = 10; + index = index + 2; + + timeData = gmtime(&date); + + fig0_10->RFU = 0; + fig0_10->setMJD(gregorian2mjd(timeData->tm_year + 1900, + timeData->tm_mon + 1, + timeData->tm_mday)); + fig0_10->LSI = 0; + fig0_10->ConfInd = (m_watermarkData[m_watermarkPos >> 3] >> + (7 - (m_watermarkPos & 0x07))) & 1; + if (++m_watermarkPos == m_watermarkSize) { + m_watermarkPos = 0; + } + fig0_10->UTC = 0; + fig0_10->setHours(timeData->tm_hour); + fig0_10->Minutes = timeData->tm_min; + index = index + 4; + figSize += 6; + + fig0_9 = (FIGtype0_9*)&etiFrame[index]; + fig0_9->FIGtypeNumber = 0; + fig0_9->Length = 4; + fig0_9->CN = 0; + fig0_9->OE = 0; + fig0_9->PD = 0; + fig0_9->Extension = 9; + + fig0_9->ext = 0; + fig0_9->lto = 0; // Unique LTO for ensemble + + if (ensemble->lto_auto) { + time_t now = time(NULL); + struct tm* ltime = localtime(&now); + time_t now2 = timegm(ltime); + ensemble->lto = (now2 - now) / 1800; + } + + if (ensemble->lto >= 0) { + fig0_9->ensembleLto = ensemble->lto; + } + else { + /* Convert to 1-complement representation */ + fig0_9->ensembleLto = (-ensemble->lto) | (1<<5); + } + + fig0_9->ensembleEcc = ensemble->ecc; + fig0_9->tableId = ensemble->international_table; + index += 5; + figSize += 5; + + break; + } + + assert(figSize <= 30); + memcpy(&etiFrame[index], Padding_FIB, 30 - figSize); + index += 30 - figSize; + + CRCtmp = 0xffff; + CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); + CRCtmp ^= 0xffff; + etiFrame[index++] = ((char *) &CRCtmp)[1]; + etiFrame[index++] = ((char *) &CRCtmp)[0]; + + + figSize = 0; + // FIB 2 insertion + if (rotateFIB < ensemble->services.size()) { + service = ensemble->services.begin() + rotateFIB; + + // FIG type 1/1, SI, Service label, one instance per subchannel + if ((*service)->getType(ensemble) == 0) { + fig1_1 = (FIGtype1_1 *) & etiFrame[index]; + + fig1_1->FIGtypeNumber = 1; + fig1_1->Length = 21; + fig1_1->Charset = 0; + fig1_1->OE = 0; + fig1_1->Extension = 1; + + fig1_1->Sld = htons((*service)->id); + index += 4; + figSize += 4; + } + else { + fig1_5 = (FIGtype1_5 *) & etiFrame[index]; + fig1_5->FIGtypeNumber = 1; + fig1_5->Length = 23; + fig1_5->Charset = 0; + fig1_5->OE = 0; + fig1_5->Extension = 5; + + fig1_5->SId = htonl((*service)->id); + index += 6; + figSize += 6; + } + (*service)->label.writeLabel(&etiFrame[index]); + index += 16; + figSize += 16; + etiFrame[index++] = (*service)->label.flag() >> 8; + etiFrame[index++] = (*service)->label.flag() & 0xFF; + figSize += 2; + } + else if (rotateFIB < + ensemble->services.size() + ensemble->components.size()) { + component = ensemble->components.begin() + + (rotateFIB - ensemble->services.size()); + + service = getService(*component, ensemble->services); + + subchannel = + getSubchannel(ensemble->subchannels, (*component)->subchId); + + if (not (*component)->label.long_label().empty() ) { + if ((*service)->getType(ensemble) == 0) { // Programme + FIGtype1_4_programme *fig1_4; + fig1_4 = (FIGtype1_4_programme*)&etiFrame[index]; + + fig1_4->FIGtypeNumber = 1; + fig1_4->Length = 22; + fig1_4->Charset = 0; + fig1_4->OE = 0; + fig1_4->Extension = 4; + fig1_4->PD = 0; + fig1_4->rfa = 0; + fig1_4->SCIdS = (*component)->SCIdS; + + fig1_4->SId = htons((*service)->id); + index += 5; + figSize += 5; + } + else { // Data + FIGtype1_4_data *fig1_4; + fig1_4 = (FIGtype1_4_data *) & etiFrame[index]; + fig1_4->FIGtypeNumber = 1; + fig1_4->Length = 24; + fig1_4->Charset = 0; + fig1_4->OE = 0; + fig1_4->Extension = 4; + fig1_4->PD = 1; + fig1_4->rfa = 0; + fig1_4->SCIdS = (*component)->SCIdS; + + fig1_4->SId = htonl((*service)->id); + index += 7; + figSize += 7; + } + (*component)->label.writeLabel(&etiFrame[index]); + index += 16; + figSize += 16; + + etiFrame[index++] = (*component)->label.flag() >> 8; + etiFrame[index++] = (*component)->label.flag() & 0xFF; + figSize += 2; + } + } + memcpy(&etiFrame[index], Padding_FIB, 30 - figSize); + index += 30 - figSize; + + CRCtmp = 0xffff; + CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); + CRCtmp ^= 0xffff; + etiFrame[index++] = ((char *) &CRCtmp)[1]; + etiFrame[index++] = ((char *) &CRCtmp)[0]; + + /* ETSI EN 300 799 Table 2: + * Only TM3 has a FIB count to CIF count that is + * not 3 to 1. + */ + if (ensemble->mode == 3) { + memcpy(&etiFrame[index], Padding_FIB, 30); + index += 30; + + CRCtmp = 0xffff; + CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); + CRCtmp ^= 0xffff; + etiFrame[index++] = ((char *) &CRCtmp)[1]; + etiFrame[index++] = ((char *) &CRCtmp)[0]; + } + + if (ensemble->services.size() > 30) { + etiLog.log(error, + "Sorry, but this software currently can't write " + "Service Label of more than 30 services.\n"); + throw MuxInitException(); + } + + // counter for FIG 0/0 + insertFIG = (insertFIG + 1) % 16; + + // We rotate through the FIBs every 30 frames + rotateFIB = (rotateFIB + 1) % 30; + + /********************************************************************** + ****** Input Data Reading ******************************************* + **********************************************************************/ + + for (subchannel = ensemble->subchannels.begin(); + subchannel != ensemble->subchannels.end(); + ++subchannel) { + + TagESTn* tag = edi_subchannelToTag[*subchannel]; + + int sizeSubchannel = getSizeByte(*subchannel); + int result = (*subchannel)->input->readFrame( + &etiFrame[index], sizeSubchannel); + + if (result < 0) { + etiLog.log(info, + "Subchannel %d read failed at ETI frame number: %d\n", + (*subchannel)->id, currentFrame); + } + + // save pointer to Audio or Data Stream into correct TagESTn for EDI + tag->mst_data = &etiFrame[index]; + + index += sizeSubchannel; + } + + + index = (3 + fc->NST + FICL) * 4; + for (subchannel = ensemble->subchannels.begin(); + subchannel != ensemble->subchannels.end(); + ++subchannel) { + index += getSizeByte(*subchannel); + } + + /******* Section EOF **************************************************/ + // End of Frame, 4 octets + index = (FLtmp + 1 + 1) * 4; + eti_EOF *eof = (eti_EOF *) & etiFrame[index]; + + // CRC of Main Stream data (MST), 16 bits + index = ((fc->NST) + 2 + 1) * 4; // MST position + + unsigned short MSTsize = ((FLtmp) - 1 - (fc->NST)) * 4; // data size + + CRCtmp = 0xffff; + CRCtmp = crc16(CRCtmp, &etiFrame[index], MSTsize); + CRCtmp ^= 0xffff; + eof->CRC = htons(CRCtmp); + + //RFU, Reserved for future use, 2 bytes, should be 0xFFFF + eof->RFU = htons(0xFFFF); + + /******* Section TIST *************************************************/ + // TimeStamps, 24 bits + 1 octet + index = (FLtmp + 2 + 1) * 4; + eti_TIST *tist = (eti_TIST *) & etiFrame[index]; + + bool enableTist = m_pt.get("general.tist", false); + if (enableTist) { + tist->TIST = htonl(timestamp) | 0xff; + } + else { + tist->TIST = htonl(0xffffff) | 0xff; + } + + timestamp += 3 << 17; + if (timestamp > 0xf9ffff) + { + timestamp -= 0xfa0000; + + // Also update MNSC time for next frame + MNSC_increment_time = true; + } + + + + /********************************************************************** + *********** Section FRPD ***************************************** + **********************************************************************/ + + int frame_size = (FLtmp + 1 + 1 + 1 + 1) * 4; + + // Give the data to the outputs + for (auto output : outputs) { + if (output->Write(etiFrame, frame_size) == -1) { + etiLog.level(error) << + "Can't write to output " << + output->get_info(); + } + } + +#ifdef DUMP_BRIDGE + dumpBytes(dumpData, sizeSubChannel, stderr); +#endif // DUMP_BRIDGE + +#if HAVE_OUTPUT_EDI + /********************************************************************** + *********** Finalise and send EDI ******************************** + **********************************************************************/ + + if (edi_conf.enabled) { + // put tags *ptr, DETI and all subchannels into one TagPacket + edi_tagpacket.tag_items.push_back(&edi_tagStarPtr); + edi_tagpacket.tag_items.push_back(&edi_tagDETI); + + list<TagESTn>::iterator tag; + for (tag = edi_subchannels.begin(); tag != edi_subchannels.end(); ++tag) { + edi_tagpacket.tag_items.push_back(&(*tag)); + } + + // Assemble into one AF Packet + AFPacket edi_afpacket = edi_afPacketiser.Assemble(edi_tagpacket); + + if (edi_conf.enable_pft) { + // Apply PFT layer to AF Packet (Reed Solomon FEC and Fragmentation) + vector< PFTFragment > edi_fragments = + edi_pft.Assemble(edi_afpacket); + + // Send over ethernet + vector< vector<uint8_t> >::iterator edi_frag; + for (edi_frag = edi_fragments.begin(); + edi_frag != edi_fragments.end(); + ++edi_frag) { + + UdpPacket udppacket; + + InetAddress& addr = udppacket.getAddress(); + addr.setAddress(edi_conf.dest_addr.c_str()); + addr.setPort(edi_conf.dest_port); + + udppacket.addData(&(edi_frag->front()), edi_frag->size()); + + edi_output.send(udppacket); + + if (edi_conf.dump) { + std::ostream_iterator<uint8_t> debug_iterator(edi_debug_file); + std::copy(edi_frag->begin(), edi_frag->end(), debug_iterator); + } + } + + if (edi_conf.verbose) { + fprintf(stderr, "EDI number of PFT fragments %zu\n", + edi_fragments.size()); + } + } + else { + // Send over ethernet + + UdpPacket udppacket; + + InetAddress& addr = udppacket.getAddress(); + addr.setAddress(edi_conf.dest_addr.c_str()); + addr.setPort(edi_conf.dest_port); + + udppacket.addData(&(edi_afpacket.front()), edi_afpacket.size()); + + edi_output.send(udppacket); + } + + if (edi_conf.dump) { + std::ostream_iterator<uint8_t> debug_iterator(edi_debug_file); + std::copy(edi_afpacket.begin(), edi_afpacket.end(), debug_iterator); + } + } +#endif // HAVE_OUTPUT_EDI + + if (currentFrame % 100 == 0) { + etiLog.log(info, "ETI frame number %i Time: %d, no TIST\n", + currentFrame, mnsc_time.tv_sec); + } + +#if _DEBUG + /********************************************************************** + *********** Output a small message ********************************* + **********************************************************************/ + if (currentFrame % 100 == 0) { + if (enableTist) { + etiLog.log(info, "ETI frame number %i Timestamp: %d + %f\n", + currentFrame, mnsc_time.tv_sec, + (timestamp & 0xFFFFFF) / 16384000.0); + } + else { + etiLog.log(info, "ETI frame number %i Time: %d, no TIST\n", + currentFrame, mnsc_time.tv_sec); + } + } +#endif + + currentFrame++; +} + +void DabMultiplexer::print_info(void) +{ + return; + // Print settings before starting + etiLog.log(info, "--- Multiplex configuration ---"); + printEnsemble(ensemble); + + etiLog.log(info, "--- Subchannels list ---"); + printSubchannels(ensemble->subchannels); + + etiLog.log(info, "--- Services list ---"); + printServices(ensemble->services); + + etiLog.log(info, "--- Components list ---"); + printComponents(ensemble->components); +} + diff --git a/src/DabMultiplexer.h b/src/DabMultiplexer.h new file mode 100644 index 0000000..27cc063 --- /dev/null +++ b/src/DabMultiplexer.h @@ -0,0 +1,510 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux 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-DabMux 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-DabMux. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __DAB_MULTIPLEXER_H__ +#define __DAB_MULTIPLEXER_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "dabOutput/dabOutput.h" +#include "dabOutput/edi/TagItems.h" +#include "dabOutput/edi/TagPacket.h" +#include "dabOutput/edi/AFPacket.h" +#include "dabOutput/edi/PFT.h" +#include "crc.h" +#include "utils.h" +#include "UdpSocket.h" +#include "InetAddress.h" +#include "dabUtils.h" +#include "PcDebug.h" +#include "MuxElements.h" +#include "RemoteControl.h" +#include "Eti.h" +#include <exception> +#include <vector> +#include <memory> +#include <string> +#include <boost/shared_ptr.hpp> +#include <boost/property_tree/ptree.hpp> + +class MuxInitException : public std::exception +{ + public: + MuxInitException(const std::string m = "ODR-DabMux initialisation error") + throw() + : msg(m) {} + ~MuxInitException(void) throw() {} + const char* what() const throw() { return msg.c_str(); } + private: + std::string msg; +}; + +class DabMultiplexer { + public: + DabMultiplexer(boost::shared_ptr<BaseRemoteController> rc, + boost::property_tree::ptree pt); + void prepare(void); + + unsigned long getCurrentFrame() { return currentFrame; } + + void mux_frame(std::vector<boost::shared_ptr<DabOutput> >& outputs); + + void print_info(void); + + void update_config(boost::property_tree::ptree pt); + + void set_edi_config(const edi_configuration_t& new_edi_conf); + + private: + void prepare_watermark(void); + void prepare_subchannels(void); + void prepare_services_components(void); + void prepare_data_inputs(void); + void reconfigure(void); + + boost::property_tree::ptree m_pt; + boost::shared_ptr<BaseRemoteController> m_rc; + + unsigned timestamp; + bool MNSC_increment_time; + struct timeval mnsc_time; + + edi_configuration_t edi_conf; + + + uint8_t m_watermarkData[128]; + size_t m_watermarkSize; + size_t m_watermarkPos; + + uint32_t sync; + unsigned long currentFrame; + + /* FIG carousel logic and iterators */ + unsigned int insertFIG; + unsigned int rotateFIB; + + std::vector<std::shared_ptr<DabService> >::iterator serviceProgFIG0_2; + std::vector<std::shared_ptr<DabService> >::iterator serviceDataFIG0_2; + std::vector<std::shared_ptr<DabService> >::iterator serviceFIG0_17; + + std::vector<DabComponent*>::iterator componentProgFIG0_8; + std::vector<DabComponent*>::iterator componentDataFIG0_8; + std::vector<DabComponent*>::iterator componentFIG0_13; + // Alternate between programme and data + bool transmitFIG0_13programme; + + + std::vector<dabSubchannel*>::iterator subchannelFIG0_1; + + boost::shared_ptr<dabEnsemble> ensemble; + + // Multiplex reconfiguration requires two sets of configurations + boost::property_tree::ptree m_pt_next; + boost::shared_ptr<dabEnsemble> ensemble_next; + +#if HAVE_OUTPUT_EDI + std::ofstream edi_debug_file; + UdpSocket edi_output; + + // The TagPacket will then be placed into an AFPacket + AFPacketiser edi_afPacketiser; + + // The AF Packet will be protected with reed-solomon and split in fragments + PFT edi_pft; +#endif // HAVE_OUTPUT_EDI +}; + +// DAB Mode +#define DEFAULT_DAB_MODE 2 + +// Taille de la trame de donnee, sous-canal 3, nb de paquets de 64bits, +// STL3 * 8 = x kbytes par trame ETI + +// Data bitrate in kbits/s. Must be 64 kb/s multiple. +#define DEFAULT_DATA_BITRATE 384 +#define DEFAULT_PACKET_BITRATE 32 + +/* default ensemble parameters. Label must be max 16 chars, short label + * a subset of the label, max 8 chars + */ +#define DEFAULT_ENSEMBLE_LABEL "ODR Dab Mux" +#define DEFAULT_ENSEMBLE_SHORT_LABEL "ODRMux" +#define DEFAULT_ENSEMBLE_ID 0xc000 +#define DEFAULT_ENSEMBLE_ECC 0xa1 + +// start value for default service IDs (if not overridden by configuration) +#define DEFAULT_SERVICE_ID 50 +#define DEFAULT_PACKET_ADDRESS 0 + + +/***************************************************************************** + ***************** Definition of FIG structures **************************** + *****************************************************************************/ + +#ifdef _WIN32 +# pragma pack(push) +#endif + +struct FIGtype0 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; +} PACKED; + + +struct FIGtype0_0 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; + + uint16_t EId; + uint8_t CIFcnt_hight:5; + uint8_t Al:1; + uint8_t Change:2; + uint8_t CIFcnt_low:8; +} PACKED; + + +struct FIGtype0_2 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; +} PACKED; + + +struct FIGtype0_2_Service { + uint16_t SId; + uint8_t NbServiceComp:4; + uint8_t CAId:3; + uint8_t Local_flag:1; +} PACKED; + + +struct FIGtype0_2_Service_data { + uint32_t SId; + uint8_t NbServiceComp:4; + uint8_t CAId:3; + uint8_t Local_flag:1; +} PACKED; + + +struct FIGtype0_2_audio_component { + uint8_t ASCTy:6; + uint8_t TMid:2; + uint8_t CA_flag:1; + uint8_t PS:1; + uint8_t SubChId:6; +} PACKED; + + +struct FIGtype0_2_data_component { + uint8_t DSCTy:6; + uint8_t TMid:2; + uint8_t CA_flag:1; + uint8_t PS:1; + uint8_t SubChId:6; +} PACKED; + + +struct FIGtype0_2_packet_component { + uint8_t SCId_high:6; + uint8_t TMid:2; + uint8_t CA_flag:1; + uint8_t PS:1; + uint8_t SCId_low:6; + void setSCId(uint16_t SCId) { + SCId_high = SCId >> 6; + SCId_low = SCId & 0x3f; + } +} PACKED; + + +struct FIGtype0_3_header { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; +} PACKED; + + +/* Warning: When bit SCCA_flag is unset(0), the multiplexer R&S does not send + * the SCCA field. But, in the Factum ETI analyzer, if this field is not there, + * it is an error. + */ +struct FIGtype0_3_data { + uint8_t SCId_high; + uint8_t SCCA_flag:1; + uint8_t rfa:3; + uint8_t SCId_low:4; + uint8_t DSCTy:6; + uint8_t rfu:1; + uint8_t DG_flag:1; + uint8_t Packet_address_high:2; + uint8_t SubChId:6; + uint8_t Packet_address_low; + uint16_t SCCA; + void setSCId(uint16_t SCId) { + SCId_high = SCId >> 4; + SCId_low = SCId & 0xf; + } + void setPacketAddress(uint16_t address) { + Packet_address_high = address >> 8; + Packet_address_low = address & 0xff; + } +} PACKED; + + +struct FIGtype0_8_short { + uint8_t SCIdS:4; + uint8_t rfa_1:3; + uint8_t ext:1; + uint8_t Id:6; + uint8_t MscFic:1; + uint8_t LS:1; + uint8_t rfa_2; +} PACKED; + + +struct FIGtype0_8_long { + uint8_t SCIdS:4; + uint8_t rfa_1:3; + uint8_t ext:1; + uint8_t SCId_high:4; + uint8_t rfa:3; + uint8_t LS:1; + uint8_t SCId_low; + uint8_t rfa_2; + void setSCId(uint16_t id) { + SCId_high = id >> 8; + SCId_low = id & 0xff; + } + uint16_t getSCid() { + return (SCId_high << 8) | SCId_low; + } +} PACKED; + + +struct FIGtype0_9 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; + + uint8_t ensembleLto:6; + uint8_t lto:1; + uint8_t ext:1; + uint8_t ensembleEcc; + uint8_t tableId; +} PACKED; + + +struct FIGtype0_10 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; + + uint8_t MJD_high:7; + uint8_t RFU:1; + uint8_t MJD_med; + uint8_t Hours_high:3; + uint8_t UTC:1; + uint8_t ConfInd:1; + uint8_t LSI:1; + uint8_t MJD_low:2; + uint8_t Minutes:6; + uint8_t Hours_low:2; + void setMJD(uint32_t date) { + MJD_high = (date >> 10) & 0x7f; + MJD_med = (date >> 2) & 0xff; + MJD_low = date & 0x03; + } + void setHours(uint16_t hours) { + Hours_high = (hours >> 2) & 0x07; + Hours_low = hours & 0x03; + } +} PACKED; + + +struct FIGtype0_17_programme { + uint16_t SId; + uint8_t NFC:2; + uint8_t Rfa:2; + uint8_t CC:1; + uint8_t L:1; + uint8_t PS:1; + uint8_t SD:1; +} PACKED; + + +struct FIGtype0_1 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:5; + uint8_t PD:1; + uint8_t OE:1; + uint8_t CN:1; +} PACKED; + + +struct FIG_01_SubChannel_ShortF { + uint8_t StartAdress_high:2; + uint8_t SubChId:6; + uint8_t StartAdress_low:8; + uint8_t TableIndex:6; + uint8_t TableSwitch:1; + uint8_t Short_Long_form:1; +} PACKED; + + +struct FIG_01_SubChannel_LongF { + uint8_t StartAdress_high:2; + uint8_t SubChId:6; + uint8_t StartAdress_low:8; + uint8_t Sub_ChannelSize_high:2; + uint8_t ProtectionLevel:2; + uint8_t Option:3; + uint8_t Short_Long_form:1; + uint8_t Sub_ChannelSize_low:8; +} PACKED; + + +// See EN 300 401, Clause 8.1.20 for the FIG0_13 description +struct FIG0_13_shortAppInfo { + uint16_t SId; + uint8_t No:4; + uint8_t SCIdS:4; +} PACKED; + + +struct FIG0_13_longAppInfo { + uint32_t SId; + uint8_t No:4; + uint8_t SCIdS:4; +} PACKED; + + +struct FIG0_13_app { + uint8_t typeHigh; + uint8_t length:5; + uint8_t typeLow:3; + void setType(uint16_t type) { + typeHigh = type >> 3; + typeLow = type & 0x1f; + } + uint32_t xpad; +} PACKED; + +#define FIG0_13_APPTYPE_SLIDESHOW 0x2 +#define FIG0_13_APPTYPE_WEBSITE 0x3 +#define FIG0_13_APPTYPE_TPEG 0x4 +#define FIG0_13_APPTYPE_DGPS 0x5 +#define FIG0_13_APPTYPE_TMC 0x6 +#define FIG0_13_APPTYPE_EPG 0x7 +#define FIG0_13_APPTYPE_DABJAVA 0x8 +#define FIG0_13_APPTYPE_JOURNALINE 0x441 + + +struct FIGtype1_0 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:3; + uint8_t OE:1; + uint8_t Charset:4; + + uint16_t EId; +} PACKED; + + +struct FIGtype1_1 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:3; + uint8_t OE:1; + uint8_t Charset:4; + + uint16_t Sld; +} PACKED; + + +struct FIGtype1_5 { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:3; + uint8_t OE:1; + uint8_t Charset:4; + uint32_t SId; +} PACKED; + + +struct FIGtype1_4_programme { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:3; + uint8_t OE:1; + uint8_t Charset:4; + uint8_t SCIdS:4; + uint8_t rfa:3; + uint8_t PD:1; + uint16_t SId; +} PACKED; + + +struct FIGtype1_4_data { + uint8_t Length:5; + uint8_t FIGtypeNumber:3; + uint8_t Extension:3; + uint8_t OE:1; + uint8_t Charset:4; + uint8_t SCIdS:4; + uint8_t rfa:3; + uint8_t PD:1; + uint32_t SId; +} PACKED; + + +#ifdef _WIN32 +# pragma pack(pop) +#endif + +#endif + diff --git a/src/DabMux.cpp b/src/DabMux.cpp index 8ef30ba..20dc31d 100644 --- a/src/DabMux.cpp +++ b/src/DabMux.cpp @@ -29,6 +29,7 @@ # include "config.h" #endif +#include <boost/shared_ptr.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/info_parser.hpp> #include <cstdio> @@ -138,51 +139,6 @@ using boost::property_tree::ptree; using boost::property_tree::ptree_error; -/* Global stats and config server */ -ManagementServer* mgmt_server; - -class MuxInitException : public exception -{ - public: - MuxInitException(const string m = "DABMUX initialisation error") throw() - : msg(m) {} - ~MuxInitException(void) throw() {} - const char* what() const throw() { return msg.c_str(); } - private: - string msg; -}; - -static unsigned char Padding_FIB[] = { - 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - - -// Protection levels and bitrates for UEP. -const unsigned char ProtectionLevelTable[64] = { - 4, 3, 2, 1, 0, 4, 3, 2, - 1, 0, 4, 3, 2, 1, 4, 3, - 2, 1, 0, 4, 3, 2, 1, 0, - 4, 3, 2, 1, 0, 4, 3, 2, - 1, 4, 3, 2, 1, 0, 4, 3, - 2, 1, 0, 4, 3, 2, 1, 0, - 4, 3, 2, 1, 0, 4, 3, 2, - 1, 0, 4, 3, 1, 4, 2, 0 -}; - -const unsigned short BitRateTable[64] = { - 32, 32, 32, 32, 32, 48, 48, 48, - 48, 48, 56, 56, 56, 56, 64, 64, - 64, 64, 64, 80, 80, 80, 80, 80, - 96, 96, 96, 96, 96, 112, 112, 112, - 112, 128, 128, 128, 128, 128, 160, 160, - 160, 160, 160, 192, 192, 192, 192, 192, - 224, 224, 224, 224, 224, 256, 256, 256, - 256, 256, 320, 320, 320, 384, 384, 384 -}; - volatile sig_atomic_t running = 1; @@ -247,117 +203,23 @@ int main(int argc, char *argv[]) strerror(errno)); } #else - if (setpriority(PRIO_PROCESS, 0, -20) == -1) { - etiLog.log(warn, "Can't increase priority: %s\n", - strerror(errno)); + // Use the lowest real-time priority for this thread, and switch to real-time scheduling + const int policy = SCHED_RR; + sched_param sp; + sp.sched_priority = sched_get_priority_min(policy); + int thread_prio_ret = pthread_setschedparam(pthread_self(), policy, &sp); + if (thread_prio_ret != 0) { + etiLog.level(error) << "Could not set real-time priority for thread:" << thread_prio_ret; } #endif - /*sched_param scheduler; - scheduler.sched_priority = 99; // sched_get_priority_max(SCHED_RR) - if (sched_setscheduler(0, SCHED_RR, &scheduler) == -1) { - etiLog.log(warn, "Can't increased priority: %s\n", - strerror(errno)); - }*/ - - uint8_t watermarkData[128]; - size_t watermarkSize = 0; - size_t watermarkPos = 0; - { - uint8_t buffer[sizeof(watermarkData) / 2]; - snprintf((char*)buffer, sizeof(buffer), - "%s %s, compiled at %s, %s", - PACKAGE_NAME, PACKAGE_VERSION, __DATE__, __TIME__); - memset(watermarkData, 0, sizeof(watermarkData)); - watermarkData[0] = 0x55; // Sync - watermarkData[1] = 0x55; - watermarkSize = 16; - for (unsigned i = 0; i < strlen((char*)buffer); ++i) { - for (int j = 0; j < 8; ++j) { - uint8_t bit = (buffer[watermarkPos >> 3] >> (7 - (watermarkPos & 0x07))) & 1; - watermarkData[watermarkSize >> 3] |= bit << (7 - (watermarkSize & 0x07)); - ++watermarkSize; - bit = 1; - watermarkData[watermarkSize >> 3] |= bit << (7 - (watermarkSize & 0x07)); - ++watermarkSize; - ++watermarkPos; - } - } - } - watermarkPos = 0; - - - dabEnsemble* ensemble = new dabEnsemble; - ensemble->label.setLabel(DEFAULT_ENSEMBLE_LABEL, DEFAULT_ENSEMBLE_SHORT_LABEL); - ensemble->mode = DEFAULT_DAB_MODE; - ensemble->id = DEFAULT_ENSEMBLE_ID; - ensemble->ecc = DEFAULT_ENSEMBLE_ECC; - - vector<dabOutput*> outputs; - vector<DabService*>::iterator service = ensemble->services.end(); - vector<DabService*>::iterator serviceProgFIG0_2; - vector<DabService*>::iterator serviceDataFIG0_2; - vector<DabService*>::iterator serviceFIG0_17; - vector<DabComponent*>::iterator component = ensemble->components.end(); - vector<DabComponent*>::iterator componentProgFIG0_8; - vector<DabComponent*>::iterator componentFIG0_13; - bool transmitFIG0_13programme = false; // Alternate between programme and data - vector<DabComponent*>::iterator componentDataFIG0_8; - vector<dabSubchannel*>::iterator subchannel = ensemble->subchannels.end(); - vector<dabSubchannel*>::iterator subchannelFIG0_1; - vector<dabOutput*>::iterator output; - dabProtection* protection = NULL; - - BaseRemoteController* rc = NULL; - - unsigned long currentFrame; - int returnCode = 0; - int cur; - unsigned char etiFrame[6144]; - unsigned short index = 0; - // FIC Length, DAB Mode I, II, IV -> FICL = 24, DAB Mode III -> FICL = 32 - unsigned FICL = (ensemble->mode == 3 ? 32 : 24); - - uint32_t sync = 0x49C5F8; - unsigned short FLtmp = 0; - unsigned short nbBytesCRC = 0; - unsigned short CRCtmp = 0xFFFF; - unsigned short MSTsize = 0; - - unsigned int insertFIG = 0; - unsigned int rotateFIB = 0; - - bool factumAnalyzer = false; - unsigned long limit = 0; - time_t date; - bool enableTist = false; - unsigned timestamp = 0; - - int mgmtserverport = 0; - - edi_configuration_t edi_conf; - - // Defaults for edi - edi_conf.enabled = false; - edi_conf.dest_addr = ""; - edi_conf.dest_port = 0; - edi_conf.source_port = 0; - edi_conf.dump = false; - edi_conf.enable_pft = false; - ptree pt; - struct timeval mnsc_time; - /* TODO: - * In a SFN, when reconfiguring the ensemble, the multiplexer - * has to be restarted (odr-dabmux doesn't support reconfiguration). - * Ideally, we must be able to restart transmission s.t. the receiver - * synchronisation is preserved. - */ - gettimeofday(&mnsc_time, NULL); + int returnCode = 0; - bool MNSC_increment_time = false; + ptree pt; + std::vector<boost::shared_ptr<DabOutput> > outputs; try { if (argc == 2) { // Assume the only argument is a config file @@ -365,19 +227,15 @@ int main(int argc, char *argv[]) if (conf_file == "-h") { printUsage(argv[0], stdout); - throw MuxInitException(); + throw MuxInitException("Nothing to do"); } try { read_info(conf_file, pt); - parse_ptree(pt, outputs, ensemble, &enableTist, &FICL, - &factumAnalyzer, &limit, &rc, &mgmtserverport, &edi_conf); } catch (runtime_error &e) { - etiLog.log(error, "Configuration file parsing error: %s\n", - e.what()); - throw MuxInitException(); + throw MuxInitException(e.what()); } } else if (argc > 1 && strncmp(argv[1], "-e", 2) == 0) { // use external config file @@ -391,14 +249,9 @@ int main(int argc, char *argv[]) string conf_file = argv[2]; read_info(conf_file, pt); - - parse_ptree(pt, outputs, ensemble, &enableTist, &FICL, - &factumAnalyzer, &limit, &rc, &mgmtserverport, &edi_conf); } catch (runtime_error &e) { - etiLog.log(error, "Configuration file parsing error: %s\n", - e.what()); - throw MuxInitException(); + throw MuxInitException(e.what()); } } #if ENABLE_CMDLINE_OPTIONS @@ -410,22 +263,31 @@ int main(int argc, char *argv[]) } #else else { - etiLog.level(error) << "You must specify the configuration file"; - throw MuxInitException(); + throw MuxInitException("No configuration file specified"); } #endif - if (mgmtserverport != 0) { - mgmt_server = new ManagementServer(mgmtserverport); + int mgmtserverport = pt.get<int>("general.managementport", + pt.get<int>("general.statsserverport", 0) ); + + /* Management: stats and config server */ + get_mgmt_server().open(mgmtserverport); + + /************** READ REMOTE CONTROL PARAMETERS *************/ + int telnetport = pt.get<int>("remotecontrol.telnetport", 0); + + boost::shared_ptr<BaseRemoteController> rc; + + if (telnetport != 0) { + rc = boost::shared_ptr<RemoteControllerTelnet>( + new RemoteControllerTelnet(telnetport)); } else { - mgmt_server = new ManagementServer(); - } - - if (rc) { - ensemble->enrol_at(*rc); + rc = boost::shared_ptr<RemoteControllerDummy>( + new RemoteControllerDummy()); } + DabMultiplexer mux(rc, pt); etiLog.level(info) << PACKAGE_NAME << " " << @@ -436,1759 +298,181 @@ int main(int argc, char *argv[]) #endif " starting up"; - if (outputs.size() == 0) { - etiLog.log(emerg, "no output defined"); - throw MuxInitException(); - } - // Check and adjust subchannels - { - set<unsigned char> ids; - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - if (ids.find((*subchannel)->id) != ids.end()) { - etiLog.log(error, - "Subchannel %u is set more than once!\n", - (*subchannel)->id); - returnCode = -1; - throw MuxInitException(); - } + edi_configuration_t edi_conf; + + /******************** READ OUTPUT PARAMETERS ***************/ + set<string> all_output_names; + ptree pt_outputs = pt.get_child("outputs"); + for (auto ptree_pair : pt_outputs) { + string outputuid = ptree_pair.first; + + // check for uniqueness of the uid + if (all_output_names.count(outputuid) == 0) { + all_output_names.insert(outputuid); + } + else { + stringstream ss; + ss << "output with uid " << outputuid << " not unique!"; + throw runtime_error(ss.str()); } - } - // Check and adjust services and components - { - set<uint32_t> ids; - for (service = ensemble->services.begin(); - service != ensemble->services.end(); - ++service) { - if (ids.find((*service)->id) != ids.end()) { - etiLog.log(error, - "Service id 0x%x (%u) is set more than once!\n", - (*service)->id, (*service)->id); - returnCode = -1; - throw MuxInitException(); - } + if (outputuid == "edi") { +#if HAVE_OUTPUT_EDI + ptree pt_edi = pt_outputs.get_child("edi"); - // Get first component of this service - component = getComponent(ensemble->components, (*service)->id); - if (component == ensemble->components.end()) { - etiLog.log(error, - "Service id 0x%x (%u) includes no component!\n", - (*service)->id, (*service)->id); - returnCode = -1; - throw MuxInitException(); - } + edi_conf.enabled = true; - // Adjust service type from this first component - switch ((*service)->getType(ensemble)) { - case 0: // Audio - (*service)->program = true; - break; - case 1: - case 2: - case 3: - (*service)->program = false; - break; - default: - etiLog.log(error, - "Error, unknown service type: %u\n", (*service)->getType(ensemble)); - returnCode = -1; - throw MuxInitException(); - } + edi_conf.dest_addr = pt_edi.get<string>("destination"); + edi_conf.dest_port = pt_edi.get<unsigned int>("port"); + edi_conf.source_port = pt_edi.get<unsigned int>("sourceport"); - // Adjust components type for DAB+ - while (component != ensemble->components.end()) { - subchannel = - getSubchannel(ensemble->subchannels, (*component)->subchId); - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, "Error, service %u component " - "links to the invalid subchannel %u\n", - (*component)->serviceId, (*component)->subchId); - returnCode = -1; - throw MuxInitException(); - } - - protection = &(*subchannel)->protection; - switch ((*subchannel)->type) { - case Audio: - { - if (protection->form == EEP) { - (*component)->type = 0x3f; // DAB+ - } - } - break; - case DataDmb: - case Fidc: - case Packet: - break; - default: - etiLog.log(error, - "Error, unknown subchannel type\n"); - returnCode = -1; - throw MuxInitException(); - } - component = getComponent(ensemble->components, - (*service)->id, component); - } + edi_conf.dump = pt_edi.get<bool>("dump"); + edi_conf.enable_pft = pt_edi.get<bool>("enable_pft"); + edi_conf.verbose = pt_edi.get<bool>("verbose"); + + mux.set_edi_config(edi_conf); +#else + throw runtime_error("EDI output not compiled in"); +#endif } - } + else { + string uri = pt_outputs.get<string>(outputuid); + + size_t proto_pos = uri.find("://"); + if (proto_pos == std::string::npos) { + stringstream ss; + ss << "Output with uid " << outputuid << " no protocol defined!"; + throw runtime_error(ss.str()); + } + + string proto = uri.substr(0, proto_pos); + string location = uri.substr(proto_pos + 3); + DabOutput *output; - for (output = outputs.begin(); output != outputs.end() ; ++output) { - if (0) { + if (0) { #if defined(HAVE_OUTPUT_FILE) - } else if ((*output)->outputProto == "file") { - (*output)->output = new DabOutputFile(); + } else if (proto == "file") { + output = new DabOutputFile(); #endif // defined(HAVE_OUTPUT_FILE) #if defined(HAVE_OUTPUT_FIFO) - } else if ((*output)->outputProto == "fifo") { - (*output)->output = new DabOutputFifo(); + } else if (proto == "fifo") { + output = new DabOutputFifo(); #endif // !defined(HAVE_OUTPUT_FIFO) #if defined(HAVE_OUTPUT_RAW) - } else if ((*output)->outputProto == "raw") { - (*output)->output = new DabOutputRaw(); + } else if (proto == "raw") { + output = new DabOutputRaw(); #endif // defined(HAVE_OUTPUT_RAW) #if defined(HAVE_OUTPUT_UDP) - } else if ((*output)->outputProto == "udp") { - (*output)->output = new DabOutputUdp(); + } else if (proto == "udp") { + output = new DabOutputUdp(); #endif // defined(HAVE_OUTPUT_UDP) #if defined(HAVE_OUTPUT_TCP) - } else if ((*output)->outputProto == "tcp") { - (*output)->output = new DabOutputTcp(); + } else if (proto == "tcp") { + output = new DabOutputTcp(); #endif // defined(HAVE_OUTPUT_TCP) #if defined(HAVE_OUTPUT_SIMUL) - } else if ((*output)->outputProto == "simul") { - (*output)->output = new DabOutputSimul(); + } else if (proto == "simul") { + output = new DabOutputSimul(); #endif // defined(HAVE_OUTPUT_SIMUL) #if defined(HAVE_OUTPUT_ZEROMQ) - } else if ((*output)->outputProto == "zmq+tcp") { - (*output)->output = new DabOutputZMQ("tcp"); - } else if ((*output)->outputProto == "zmq+ipc") { - (*output)->output = new DabOutputZMQ("ipc"); - } else if ((*output)->outputProto == "zmq+pgm") { - (*output)->output = new DabOutputZMQ("pgm"); - } else if ((*output)->outputProto == "zmq+epgm") { - (*output)->output = new DabOutputZMQ("epgm"); + } else if (proto == "zmq+tcp") { + output = new DabOutputZMQ("tcp"); + } else if (proto == "zmq+ipc") { + output = new DabOutputZMQ("ipc"); + } else if (proto == "zmq+pgm") { + output = new DabOutputZMQ("pgm"); + } else if (proto == "zmq+epgm") { + output = new DabOutputZMQ("epgm"); #endif // defined(HAVE_OUTPUT_ZEROMQ) - } else { - etiLog.log(error, "Output protocol unknown: %s\n", - (*output)->outputProto.c_str()); - throw MuxInitException(); - } - - if ((*output)->output == NULL) { - etiLog.log(error, "Unable to init output %s://%s\n", - (*output)->outputProto.c_str(), (*output)->outputName.c_str()); - return -1; - } - if ((*output)->output->Open((*output)->outputName) - == -1) { - etiLog.log(error, "Unable to open output %s://%s\n", - (*output)->outputProto.c_str(), (*output)->outputName.c_str()); - return -1; - } - } + } else { + etiLog.level(error) << "Output protocol unknown: " << proto; + throw MuxInitException(); + } - // Prepare and check the data inputs - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - protection = &(*subchannel)->protection; - if (subchannel == ensemble->subchannels.begin()) { - (*subchannel)->startAddress = 0; - } else { - (*subchannel)->startAddress = (*(subchannel - 1))->startAddress + - getSizeCu(*(subchannel - 1)); - } - if ((*subchannel)->input->open((*subchannel)->inputUri) == -1) { - perror((*subchannel)->inputUri.c_str()); - returnCode = -1; - throw MuxInitException(); - } + if (output == NULL) { + etiLog.level(error) << + "Unable to init output " << + uri; + return -1; + } - // TODO Check errors - int subch_bitrate = (*subchannel)->input->setBitrate( (*subchannel)->bitrate); - if (subch_bitrate <= 0) { - etiLog.level(error) << "can't set bitrate for source " << - (*subchannel)->inputUri; - returnCode = -1; - throw MuxInitException(); - } - (*subchannel)->bitrate = subch_bitrate; - - /* Use EEP unless we find a UEP configuration - * UEP is only used for MPEG audio, but some bitrates don't - * have a UEP profile (EN 300 401 Clause 6.2.1). - * For these bitrates, we must switch to EEP. - * - * AAC audio and data is already EEP - */ - if (protection->form == UEP) { - protection->form = EEP; - for (int i = 0; i < 64; i++) { - if ( (*subchannel)->bitrate == BitRateTable[i] && - protection->level == ProtectionLevelTable[i] ) { - protection->form = UEP; - protection->uep.tableIndex = i; - } + if (output->Open(location) == -1) { + etiLog.level(error) << + "Unable to open output " << + uri; + return -1; } - } - /* EEP B can only be used for subchannels with bitrates - * multiple of 32kbit/s - */ - if ( protection->form == EEP && - protection->eep.profile == EEP_B && - subch_bitrate % 32 != 0 ) { - etiLog.level(error) << - "Cannot use EEP_B protection for subchannel " << - (*subchannel)->inputUri << - ": bitrate not multiple of 32kbit/s"; - returnCode = -1; - throw MuxInitException(); + boost::shared_ptr<DabOutput> dabout(output); + outputs.push_back(dabout); + } } - if (ensemble->subchannels.size() == 0) { - etiLog.log(error, "can't multiplex no subchannel!\n"); - returnCode = -1; - throw MuxInitException(); - } - subchannel = ensemble->subchannels.end() - 1; - if ((*subchannel)->startAddress + getSizeCu((*subchannel)) > 864) { - etiLog.log(error, "Total size in CU exceeds 864\n"); - printSubchannels(ensemble->subchannels); - returnCode = -1; + if (outputs.size() == 0) { + etiLog.log(emerg, "no output defined"); throw MuxInitException(); } - // Init packet components SCId - cur = 0; - for (component = ensemble->components.begin(); - component != ensemble->components.end(); - ++component) { - subchannel = getSubchannel(ensemble->subchannels, - (*component)->subchId); - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*component)->subchId, (*component)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - if ((*subchannel)->type != Packet) continue; - - (*component)->packet.id = cur++; - } - - // Print settings before starting - etiLog.log(info, "--- Multiplex configuration ---"); - printEnsemble(ensemble); - - etiLog.log(info, "--- Subchannels list ---"); - printSubchannels(ensemble->subchannels); - - etiLog.log(info, "--- Services list ---"); - printServices(ensemble->services); - - etiLog.log(info, "--- Components list ---"); - printComponents(ensemble->components); + mux.prepare(); + mux.print_info(); etiLog.log(info, "--- Output list ---"); printOutputs(outputs); +#if HAVE_OUTPUT_EDI if (edi_conf.enabled) { etiLog.level(warn) << "EXPERIMENTAL EDI OUTPUT ENABLED!"; etiLog.level(info) << "edi to " << edi_conf.dest_addr << ":" << edi_conf.dest_port; etiLog.level(info) << "source port " << edi_conf.source_port; etiLog.level(info) << "verbose " << edi_conf.verbose; } +#endif + size_t limit = pt.get("general.nbframes", 0); - /* These iterators are used to fill the respective FIG. - * It is necessary to cycle through all the FIGs that have - * to be transmitted because they do not fit in a single - * FIB. - * - * ETSI EN 300 799 Clauses 5.2 and 8.1 - */ - serviceProgFIG0_2 = ensemble->services.end(); - serviceDataFIG0_2 = ensemble->services.end(); - componentProgFIG0_8 = ensemble->components.end(); - componentFIG0_13 = ensemble->components.end(); - componentDataFIG0_8 = ensemble->components.end(); - serviceFIG0_17 = ensemble->services.end(); - subchannelFIG0_1 = ensemble->subchannels.end(); - - if (edi_conf.verbose) { - etiLog.log(info, "Setup EDI debug"); - } - std::ofstream edi_debug_file; - - if (edi_conf.dump) { - edi_debug_file.open("./edi.debug"); - } - UdpSocket edi_output; - - if (edi_conf.enabled) { - edi_output.create(edi_conf.source_port); - } - - if (edi_conf.verbose) { - etiLog.log(info, "EDI debug set up"); - } - - // The TagPacket will then be placed into an AFPacket - AFPacketiser edi_afPacketiser(edi_conf.verbose); - - // The AF Packet will be protected with reed-solomon and split in fragments - PFT edi_pft(207, 3, edi_conf); - + etiLog.level(info) << "Start loop"; /* Each iteration of the main loop creates one ETI frame */ + size_t currentFrame; for (currentFrame = 0; running; currentFrame++) { - if ((limit > 0) && (currentFrame >= limit)) { - break; - } - - // For EDI, save ETI(LI) Management data into a TAG Item DETI - TagDETI edi_tagDETI; - TagStarPTR edi_tagStarPtr; - list<TagESTn> edi_subchannels; - map<dabSubchannel*, TagESTn*> edi_subchannelToTag; - - // The above Tag Items will be assembled into a TAG Packet - TagPacket edi_tagpacket; - - edi_tagDETI.atstf = 0; // TODO add ATST support - - date = getDabTime(); - - // Initialise the ETI frame - memset(etiFrame, 0, 6144); - - /********************************************************************** - ********** Section SYNC of ETI(NI, G703) ************************* - **********************************************************************/ - - // See ETS 300 799 Clause 6 - eti_SYNC *etiSync = (eti_SYNC *) etiFrame; - - etiSync->ERR = edi_tagDETI.stat = 0xFF; // ETS 300 799, 5.2, no error + mux.mux_frame(outputs); - //****** Field FSYNC *****// - // See ETS 300 799, 6.2.1.2 - sync ^= 0xffffff; - etiSync->FSYNC = sync; - - /********************************************************************** - *********** Section LIDATA of ETI(NI, G703) ********************** - **********************************************************************/ - - // See ETS 300 799 Figure 5 for a better overview of these fields. - - //****** Section FC ***************************************************/ - // 4 octets, starts at offset 4 - eti_FC *fc = (eti_FC *) &etiFrame[4]; - - //****** FCT ******// - // Incremente for each frame, overflows at 249 - fc->FCT = currentFrame % 250; - edi_tagDETI.dflc = currentFrame % 5000; - - //****** FICF ******// - // Fast Information Channel Flag, 1 bit, =1 if FIC present - fc->FICF = edi_tagDETI.ficf = 1; - - //****** NST ******// - /* Number of audio of data sub-channels, 7 bits, 0-64. - * In the 15-frame period immediately preceding a multiplex - * re-configuration, NST can take the value 0 (see annex E). - */ - fc->NST = ensemble->subchannels.size(); - - //****** FP ******// - /* Frame Phase, 3 bit counter, tells the COFDM generator - * when to insert the TII. Is also used by the MNSC. - */ - fc->FP = edi_tagDETI.fp = currentFrame & 0x7; - - //****** MID ******// - //Mode Identity, 2 bits, 01 ModeI, 10 modeII, 11 ModeIII, 00 ModeIV - fc->MID = edi_tagDETI.mid = ensemble->mode; //mode 2 needs 3 FIB, 3*32octets = 96octets - - //****** FL ******// - /* Frame Length, 11 bits, nb of words(4 bytes) in STC, EOH and MST - * if NST=0, FL=1+FICL words, FICL=24 or 32 depending on the mode. - * The FL is given in words (4 octets), see ETS 300 799 5.3.6 for details - */ - FLtmp = 1 + FICL + (fc->NST); - - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - // Add STLsbch - FLtmp += getSizeWord(*subchannel); - } - - fc->setFrameLength(FLtmp); - index = 8; - - /******* Section STC **************************************************/ - // Stream Characterization, - // number of channels * 4 octets = nb octets total - int edi_stream_id = 1; - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - protection = &(*subchannel)->protection; - eti_STC *sstc = (eti_STC *) & etiFrame[index]; - - sstc->SCID = (*subchannel)->id; - sstc->startAddress_high = (*subchannel)->startAddress / 256; - sstc->startAddress_low = (*subchannel)->startAddress % 256; - // depends on the desired protection form - if (protection->form == UEP) { - sstc->TPL = 0x10 | - ProtectionLevelTable[protection->uep.tableIndex]; - } - else if (protection->form == EEP) { - sstc->TPL = 0x20 | (protection->eep.GetOption() << 2) | protection->level; - } - - // Sub-channel Stream Length, multiple of 64 bits - sstc->STL_high = getSizeDWord(*subchannel) / 256; - sstc->STL_low = getSizeDWord(*subchannel) % 256; - - TagESTn tag_ESTn(edi_stream_id++); - tag_ESTn.scid = (*subchannel)->id; - tag_ESTn.sad = (*subchannel)->startAddress; - tag_ESTn.tpl = sstc->TPL; - tag_ESTn.rfa = 0; // two bits - tag_ESTn.mst_length = getSizeByte(*subchannel) / 8; - assert(getSizeByte(*subchannel) % 8 == 0); - - edi_subchannels.push_back(tag_ESTn); - edi_subchannelToTag[*subchannel] = &edi_subchannels.back(); - index += 4; - } - - /******* Section EOH **************************************************/ - // End of Header 4 octets - eti_EOH *eoh = (eti_EOH *) & etiFrame[index]; - - //MNSC Multiplex Network Signalling Channel, 2 octets - - eoh->MNSC = 0; - - struct tm *time_tm = gmtime(&mnsc_time.tv_sec); - switch (fc->FP & 0x3) - { - case 0: - if (MNSC_increment_time) - { - MNSC_increment_time = false; - mnsc_time.tv_sec += 1; - } - { - - eti_MNSC_TIME_0 *mnsc = (eti_MNSC_TIME_0 *) &eoh->MNSC; - // Set fields according to ETS 300 799 -- 5.5.1 and A.2.2 - mnsc->type = 0; - mnsc->identifier = 0; - mnsc->rfa = 0; - } - break; - case 1: - { - eti_MNSC_TIME_1 *mnsc = (eti_MNSC_TIME_1 *) &eoh->MNSC; - mnsc->setFromTime(time_tm); - mnsc->accuracy = 1; - mnsc->sync_to_frame = 1; - } - break; - case 2: - { - eti_MNSC_TIME_2 *mnsc = (eti_MNSC_TIME_2 *) &eoh->MNSC; - mnsc->setFromTime(time_tm); - } - break; - case 3: - { - eti_MNSC_TIME_3 *mnsc = (eti_MNSC_TIME_3 *) &eoh->MNSC; - mnsc->setFromTime(time_tm); - } - break; - } - - edi_tagDETI.mnsc = eoh->MNSC; - - // CRC Cyclic Redundancy Checksum of the FC, STC and MNSC, 2 octets - nbBytesCRC = 4 + ((fc->NST) * 4) + 2; - - CRCtmp = 0xffff; - CRCtmp = crc16(CRCtmp, &etiFrame[4], nbBytesCRC); - CRCtmp ^= 0xffff; - eoh->CRC = htons(CRCtmp); - - /******* Section MST **************************************************/ - // Main Stream Data, if FICF=1 the first 96 or 128 bytes carry the FIC - // (depending on mode) - index = ((fc->NST) + 2 + 1) * 4; - edi_tagDETI.fic_data = &etiFrame[index]; - edi_tagDETI.fic_length = FICL * 4; - - // FIC Insertion - FIGtype0* fig0; - FIGtype0_0 *fig0_0; - FIGtype0_1 *figtype0_1; - - FIG_01_SubChannel_ShortF *fig0_1subchShort; - FIG_01_SubChannel_LongF *fig0_1subchLong1; - - FIGtype0_2 *fig0_2; - - FIGtype0_2_Service *fig0_2serviceAudio; - FIGtype0_2_Service_data *fig0_2serviceData; - FIGtype0_2_audio_component* audio_description; - FIGtype0_2_data_component* data_description; - FIGtype0_2_packet_component* packet_description; - - FIGtype0_3_header *fig0_3_header; - FIGtype0_3_data *fig0_3_data; - FIGtype0_9 *fig0_9; - FIGtype0_10 *fig0_10; - FIGtype0_17_programme *programme; - - FIGtype1_0 *fig1_0; - FIGtype1_1 *fig1_1; - FIGtype1_5 *fig1_5; - - tm* timeData; - - unsigned char figSize = 0; - - // FIB 0 Insertion - switch (insertFIG) { - - case 0: - case 4: - case 8: - case 12: - // FIG type 0/0, Multiplex Configuration Info (MCI), - // Ensemble information - fig0_0 = (FIGtype0_0 *) & etiFrame[index]; - - fig0_0->FIGtypeNumber = 0; - fig0_0->Length = 5; - fig0_0->CN = 0; - fig0_0->OE = 0; - fig0_0->PD = 0; - fig0_0->Extension = 0; - - fig0_0->EId = htons(ensemble->id); - fig0_0->Change = 0; - fig0_0->Al = 0; - fig0_0->CIFcnt_hight = (currentFrame / 250) % 20; - fig0_0->CIFcnt_low = (currentFrame % 250); - index = index + 6; - figSize += 6; - - break; - - case 1: - case 6: - case 10: - case 13: - // FIG type 0/1, MIC, Sub-Channel Organization, - // one instance of the part for each subchannel - figtype0_1 = (FIGtype0_1 *) & etiFrame[index]; - - figtype0_1->FIGtypeNumber = 0; - figtype0_1->Length = 1; - figtype0_1->CN = 0; - figtype0_1->OE = 0; - figtype0_1->PD = 0; - figtype0_1->Extension = 1; - index = index + 2; - figSize += 2; - - // Rotate through the subchannels until there is no more - // space in the FIG0/1 - if (subchannelFIG0_1 == ensemble->subchannels.end()) { - subchannelFIG0_1 = ensemble->subchannels.begin(); - } - - for (; subchannelFIG0_1 != ensemble->subchannels.end(); - ++subchannelFIG0_1) { - protection = &(*subchannelFIG0_1)->protection; - - if ( (protection->form == UEP && figSize > 27) || - (protection->form == EEP && figSize > 26) ) { - break; - } - - if (protection->form == UEP) { - fig0_1subchShort = - (FIG_01_SubChannel_ShortF*) &etiFrame[index]; - fig0_1subchShort->SubChId = (*subchannelFIG0_1)->id; - - fig0_1subchShort->StartAdress_high = - (*subchannelFIG0_1)->startAddress / 256; - fig0_1subchShort->StartAdress_low = - (*subchannelFIG0_1)->startAddress % 256; - - fig0_1subchShort->Short_Long_form = 0; - fig0_1subchShort->TableSwitch = 0; - fig0_1subchShort->TableIndex = - protection->uep.tableIndex; - - index = index + 3; - figSize += 3; - figtype0_1->Length += 3; - } - else if (protection->form == EEP) { - fig0_1subchLong1 = - (FIG_01_SubChannel_LongF*) &etiFrame[index]; - fig0_1subchLong1->SubChId = (*subchannelFIG0_1)->id; - - fig0_1subchLong1->StartAdress_high = - (*subchannelFIG0_1)->startAddress / 256; - fig0_1subchLong1->StartAdress_low = - (*subchannelFIG0_1)->startAddress % 256; - - fig0_1subchLong1->Short_Long_form = 1; - fig0_1subchLong1->Option = protection->eep.GetOption(); - fig0_1subchLong1->ProtectionLevel = - protection->level; - - fig0_1subchLong1->Sub_ChannelSize_high = - getSizeCu(*subchannelFIG0_1) / 256; - fig0_1subchLong1->Sub_ChannelSize_low = - getSizeCu(*subchannelFIG0_1) % 256; - - index = index + 4; - figSize += 4; - figtype0_1->Length += 4; - } - } + if (limit && currentFrame >= limit) { break; - - case 2: - case 9: - case 11: - case 14: - // FIG type 0/2, MCI, Service Organization, one instance of - // FIGtype0_2_Service for each subchannel - fig0_2 = NULL; - cur = 0; - - // Rotate through the subchannels until there is no more - // space in the FIG0/1 - if (serviceProgFIG0_2 == ensemble->services.end()) { - serviceProgFIG0_2 = ensemble->services.begin(); - } - - for (; serviceProgFIG0_2 != ensemble->services.end(); - ++serviceProgFIG0_2) { - if (!(*serviceProgFIG0_2)->nbComponent(ensemble->components)) { - continue; - } - - if ((*serviceProgFIG0_2)->getType(ensemble) != 0) { - continue; - } - - ++cur; - - if (fig0_2 == NULL) { - fig0_2 = (FIGtype0_2 *) & etiFrame[index]; - - fig0_2->FIGtypeNumber = 0; - fig0_2->Length = 1; - fig0_2->CN = 0; - fig0_2->OE = 0; - fig0_2->PD = 0; - fig0_2->Extension = 2; - index = index + 2; - figSize += 2; - } - - if (figSize + 3 - + (*serviceProgFIG0_2)->nbComponent(ensemble->components) - * 2 > 30) { - break; - } - - fig0_2serviceAudio = (FIGtype0_2_Service*) &etiFrame[index]; - - fig0_2serviceAudio->SId = htons((*serviceProgFIG0_2)->id); - fig0_2serviceAudio->Local_flag = 0; - fig0_2serviceAudio->CAId = 0; - fig0_2serviceAudio->NbServiceComp = - (*serviceProgFIG0_2)->nbComponent(ensemble->components); - index += 3; - fig0_2->Length += 3; - figSize += 3; - - int curCpnt = 0; - for (component = getComponent(ensemble->components, - (*serviceProgFIG0_2)->id); - component != ensemble->components.end(); - component = getComponent(ensemble->components, - (*serviceProgFIG0_2)->id, component)) { - subchannel = getSubchannel(ensemble->subchannels, - (*component)->subchId); - - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*component)->subchId, (*component)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - - switch ((*subchannel)->type) { - case Audio: - audio_description = - (FIGtype0_2_audio_component*)&etiFrame[index]; - audio_description->TMid = 0; - audio_description->ASCTy = (*component)->type; - audio_description->SubChId = (*subchannel)->id; - audio_description->PS = ((curCpnt == 0) ? 1 : 0); - audio_description->CA_flag = 0; - break; - case DataDmb: - data_description = - (FIGtype0_2_data_component*)&etiFrame[index]; - data_description->TMid = 1; - data_description->DSCTy = (*component)->type; - data_description->SubChId = (*subchannel)->id; - data_description->PS = ((curCpnt == 0) ? 1 : 0); - data_description->CA_flag = 0; - break; - case Packet: - packet_description = - (FIGtype0_2_packet_component*)&etiFrame[index]; - packet_description->TMid = 3; - packet_description->setSCId((*component)->packet.id); - packet_description->PS = ((curCpnt == 0) ? 1 : 0); - packet_description->CA_flag = 0; - break; - default: - etiLog.log(error, - "Component type not supported\n"); - returnCode = -1; - throw MuxInitException(); - } - index += 2; - fig0_2->Length += 2; - figSize += 2; - if (figSize > 30) { - etiLog.log(error, - "Sorry, no space left in FIG 0/2 to insert " - "component %i of program service %i.\n", - curCpnt, cur); - returnCode = -1; - throw MuxInitException(); - } - ++curCpnt; - } - } - break; - - case 3: - fig0_2 = NULL; - cur = 0; - - if (serviceDataFIG0_2 == ensemble->services.end()) { - serviceDataFIG0_2 = ensemble->services.begin(); - } - for (; serviceDataFIG0_2 != ensemble->services.end(); - ++serviceDataFIG0_2) { - if (!(*serviceDataFIG0_2)->nbComponent(ensemble->components)) { - continue; - } - - unsigned char type = (*serviceDataFIG0_2)->getType(ensemble); - if ((type == 0) || (type == 2)) { - continue; - } - - ++cur; - - if (fig0_2 == NULL) { - fig0_2 = (FIGtype0_2 *) & etiFrame[index]; - - fig0_2->FIGtypeNumber = 0; - fig0_2->Length = 1; - fig0_2->CN = 0; - fig0_2->OE = 0; - fig0_2->PD = 1; - fig0_2->Extension = 2; - index = index + 2; - figSize += 2; - } - - if (figSize + 5 - + (*serviceDataFIG0_2)->nbComponent(ensemble->components) - * 2 > 30) { - break; - } - - fig0_2serviceData = - (FIGtype0_2_Service_data*) &etiFrame[index]; - - fig0_2serviceData->SId = htonl((*serviceDataFIG0_2)->id); - fig0_2serviceData->Local_flag = 0; - fig0_2serviceData->CAId = 0; - fig0_2serviceData->NbServiceComp = - (*serviceDataFIG0_2)->nbComponent(ensemble->components); - fig0_2->Length += 5; - index += 5; - figSize += 5; - - int curCpnt = 0; - for (component = getComponent(ensemble->components, - (*serviceDataFIG0_2)->id); - component != ensemble->components.end(); - component = getComponent(ensemble->components, - (*serviceDataFIG0_2)->id, component)) { - subchannel = getSubchannel(ensemble->subchannels, - (*component)->subchId); - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*component)->subchId, (*component)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - - switch ((*subchannel)->type) { - case Audio: - audio_description = - (FIGtype0_2_audio_component*)&etiFrame[index]; - audio_description->TMid = 0; - audio_description->ASCTy = (*component)->type; - audio_description->SubChId = (*subchannel)->id; - audio_description->PS = ((curCpnt == 0) ? 1 : 0); - audio_description->CA_flag = 0; - break; - case DataDmb: - data_description = - (FIGtype0_2_data_component*)&etiFrame[index]; - data_description->TMid = 1; - data_description->DSCTy = (*component)->type; - data_description->SubChId = (*subchannel)->id; - data_description->PS = ((curCpnt == 0) ? 1 : 0); - data_description->CA_flag = 0; - break; - case Packet: - packet_description = - (FIGtype0_2_packet_component*)&etiFrame[index]; - packet_description->TMid = 3; - packet_description->setSCId((*component)->packet.id); - packet_description->PS = ((curCpnt == 0) ? 1 : 0); - packet_description->CA_flag = 0; - break; - default: - etiLog.log(error, - "Component type not supported\n"); - returnCode = -1; - throw MuxInitException(); - } - index += 2; - fig0_2->Length += 2; - figSize += 2; - if (figSize > 30) { - etiLog.log(error, - "Sorry, no place left in FIG 0/2 to insert " - "component %i of data service %i.\n", - curCpnt, cur); - returnCode = -1; - throw MuxInitException(); - } - ++curCpnt; - } - } - break; - - case 5: - fig0_3_header = NULL; - - for (component = ensemble->components.begin(); - component != ensemble->components.end(); - ++component) { - subchannel = getSubchannel(ensemble->subchannels, - (*component)->subchId); - - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*component)->subchId, (*component)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - - if ((*subchannel)->type != Packet) - continue; - - if (fig0_3_header == NULL) { - fig0_3_header = (FIGtype0_3_header*)&etiFrame[index]; - fig0_3_header->FIGtypeNumber = 0; - fig0_3_header->Length = 1; - fig0_3_header->CN = 0; - fig0_3_header->OE = 0; - fig0_3_header->PD = 0; - fig0_3_header->Extension = 3; - - index += 2; - figSize += 2; - } - - /* Warning: When bit SCCA_flag is unset(0), the multiplexer - * R&S does not send the SCCA field. But, in the Factum ETI - * analyzer, if this field is not there, it is an error. - */ - fig0_3_data = (FIGtype0_3_data*)&etiFrame[index]; - fig0_3_data->setSCId((*component)->packet.id); - fig0_3_data->rfa = 0; - fig0_3_data->SCCA_flag = 0; - // if 0, datagroups are used - fig0_3_data->DG_flag = !(*component)->packet.datagroup; - fig0_3_data->rfu = 0; - fig0_3_data->DSCTy = (*component)->type; - fig0_3_data->SubChId = (*subchannel)->id; - fig0_3_data->setPacketAddress((*component)->packet.address); - if (factumAnalyzer) { - fig0_3_data->SCCA = 0; - } - - fig0_3_header->Length += 5; - index += 5; - figSize += 5; - if (factumAnalyzer) { - fig0_3_header->Length += 2; - index += 2; - figSize += 2; - } - - if (figSize > 30) { - etiLog.log(error, - "can't add to Fic Fig 0/3, " - "too much packet service\n"); - returnCode = -1; - throw MuxInitException(); - } - } - break; - - case 7: - fig0 = NULL; - if (serviceFIG0_17 == ensemble->services.end()) { - serviceFIG0_17 = ensemble->services.begin(); - } - for (; serviceFIG0_17 != ensemble->services.end(); - ++serviceFIG0_17) { - - if ( (*serviceFIG0_17)->pty == 0 && - (*serviceFIG0_17)->language == 0) { - continue; - } - - if (fig0 == NULL) { - fig0 = (FIGtype0*)&etiFrame[index]; - fig0->FIGtypeNumber = 0; - fig0->Length = 1; - fig0->CN = 0; - fig0->OE = 0; - fig0->PD = 0; - fig0->Extension = 17; - index += 2; - figSize += 2; - } - - if ((*serviceFIG0_17)->language == 0) { - if (figSize + 4 > 30) { - break; - } - } - else { - if (figSize + 5 > 30) { - break; - } - } - - programme = - (FIGtype0_17_programme*)&etiFrame[index]; - programme->SId = htons((*serviceFIG0_17)->id); - programme->SD = 1; - programme->PS = 0; - programme->L = (*serviceFIG0_17)->language != 0; - programme->CC = 0; - programme->Rfa = 0; - programme->NFC = 0; - if ((*serviceFIG0_17)->language == 0) { - etiFrame[index + 3] = (*serviceFIG0_17)->pty; - fig0->Length += 4; - index += 4; - figSize += 4; - } - else { - etiFrame[index + 3] = (*serviceFIG0_17)->language; - etiFrame[index + 4] = (*serviceFIG0_17)->pty; - fig0->Length += 5; - index += 5; - figSize += 5; - } - } - break; - } - - if (figSize > 30) { - etiLog.log(error, - "FIG too big (%i > 30)\n", figSize); - returnCode = -1; - throw MuxInitException(); - } - - memcpy(&etiFrame[index], Padding_FIB, 30 - figSize); - index += 30 - figSize; - - CRCtmp = 0xffff; - CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); - CRCtmp ^= 0xffff; - etiFrame[index++] = ((char *) &CRCtmp)[1]; - etiFrame[index++] = ((char *) &CRCtmp)[0]; - - figSize = 0; - // FIB 1 insertion - switch (rotateFIB) { - case 0: // FIG 0/8 program - fig0 = NULL; - - if (componentProgFIG0_8 == ensemble->components.end()) { - componentProgFIG0_8 = ensemble->components.begin(); - } - for (; componentProgFIG0_8 != ensemble->components.end(); - ++componentProgFIG0_8) { - service = getService(*componentProgFIG0_8, - ensemble->services); - subchannel = getSubchannel(ensemble->subchannels, - (*componentProgFIG0_8)->subchId); - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*componentProgFIG0_8)->subchId, - (*componentProgFIG0_8)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - - if (!(*service)->program) - continue; - - if (fig0 == NULL) { - fig0 = (FIGtype0*)&etiFrame[index]; - fig0->FIGtypeNumber = 0; - fig0->Length = 1; - fig0->CN = 0; - fig0->OE = 0; - fig0->PD = 0; - fig0->Extension = 8; - index += 2; - figSize += 2; - } - - if ((*subchannel)->type == Packet) { // Data packet - if (figSize > 30 - 5) { - break; - } - etiFrame[index] = - ((*componentProgFIG0_8)->serviceId >> 8) & 0xFF; - etiFrame[index+1] = - ((*componentProgFIG0_8)->serviceId) & 0xFF; - fig0->Length += 2; - index += 2; - figSize += 2; - - FIGtype0_8_long* definition = - (FIGtype0_8_long*)&etiFrame[index]; - memset(definition, 0, 3); - definition->ext = 0; // no rfa - definition->SCIdS = (*componentProgFIG0_8)->SCIdS; - definition->LS = 1; - definition->setSCId((*componentProgFIG0_8)->packet.id); - fig0->Length += 3; - index += 3; // 8 minus rfa - figSize += 3; - } - else { // Audio, data stream or FIDC - if (figSize > 30 - 4) { - break; - } - etiFrame[index] = - ((*componentProgFIG0_8)->serviceId >> 8) & 0xFF; - etiFrame[index+1] = - ((*componentProgFIG0_8)->serviceId) & 0xFF; - - fig0->Length += 2; - index += 2; - figSize += 2; - - FIGtype0_8_short* definition = - (FIGtype0_8_short*)&etiFrame[index]; - memset(definition, 0, 2); - definition->ext = 0; // no rfa - definition->SCIdS = (*componentProgFIG0_8)->SCIdS; - definition->LS = 0; - definition->MscFic = 0; - definition->Id = (*componentProgFIG0_8)->subchId; - fig0->Length += 2; - index += 2; // 4 minus rfa - figSize += 2; - } - } - break; - - case 1: // FIG 0/8 data - fig0 = NULL; - - if (componentDataFIG0_8 == ensemble->components.end()) { - componentDataFIG0_8 = ensemble->components.begin(); - } - for (; componentDataFIG0_8 != ensemble->components.end(); - ++componentDataFIG0_8) { - service = getService(*componentDataFIG0_8, - ensemble->services); - - subchannel = getSubchannel(ensemble->subchannels, - (*componentDataFIG0_8)->subchId); - - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*componentDataFIG0_8)->subchId, - (*componentDataFIG0_8)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - - if ((*service)->program) - continue; - - if (fig0 == NULL) { - fig0 = (FIGtype0*)&etiFrame[index]; - fig0->FIGtypeNumber = 0; - fig0->Length = 1; - fig0->CN = 0; - fig0->OE = 0; - fig0->PD = 1; - fig0->Extension = 8; - index += 2; - figSize += 2; - } - - if ((*subchannel)->type == Packet) { // Data packet - if (figSize > 30 - 7) { - break; - } - etiFrame[index] = - ((*componentDataFIG0_8)->serviceId >> 8) & 0xFF; - etiFrame[index+1] = - ((*componentDataFIG0_8)->serviceId) & 0xFF; - fig0->Length += 4; - index += 4; - figSize += 4; - - FIGtype0_8_long* definition = - (FIGtype0_8_long*)&etiFrame[index]; - memset(definition, 0, 3); - definition->ext = 0; // no rfa - definition->SCIdS = (*componentDataFIG0_8)->SCIdS; - definition->LS = 1; - definition->setSCId((*componentDataFIG0_8)->packet.id); - fig0->Length += 3; - index += 3; // 8 minus rfa - figSize += 3; - } - else { // Audio, data stream or FIDC - if (figSize > 30 - 6) { - break; - } - etiFrame[index] = - ((*componentDataFIG0_8)->serviceId >> 8) & 0xFF; - etiFrame[index+1] = - ((*componentDataFIG0_8)->serviceId) & 0xFF; - fig0->Length += 4; - index += 4; - figSize += 4; - - FIGtype0_8_short* definition = - (FIGtype0_8_short*)&etiFrame[index]; - memset(definition, 0, 2); - definition->ext = 0; // no rfa - definition->SCIdS = (*componentDataFIG0_8)->SCIdS; - definition->LS = 0; - definition->MscFic = 0; - definition->Id = (*componentDataFIG0_8)->subchId; - fig0->Length += 2; - index += 2; // 4 minus rfa - figSize += 2; - } - } - break; - - case 3: - // FIG type 1/0, Service Information (SI), Ensemble Label - fig1_0 = (FIGtype1_0 *) & etiFrame[index]; - - fig1_0->Length = 21; - fig1_0->FIGtypeNumber = 1; - fig1_0->Extension = 0; - fig1_0->OE = 0; - fig1_0->Charset = 0; - fig1_0->EId = htons(ensemble->id); - index = index + 4; - - memcpy(&etiFrame[index], ensemble->label.text(), 16); - index = index + 16; - - etiFrame[index++] = ensemble->label.flag() >> 8; - etiFrame[index++] = ensemble->label.flag() & 0xFF; - - figSize += 22; - break; - - case 5: - case 6: - // FIG 0 / 13 - fig0 = NULL; - - if (componentFIG0_13 == ensemble->components.end()) { - componentFIG0_13 = ensemble->components.begin(); - - transmitFIG0_13programme = !transmitFIG0_13programme; - // Alternate between data and and programme FIG0/13, - // do not mix fig0 with PD=0 with extension 13 stuff - // that actually needs PD=1, and vice versa - } - - for (; componentFIG0_13 != ensemble->components.end(); - ++componentFIG0_13) { - - subchannel = getSubchannel(ensemble->subchannels, - (*componentFIG0_13)->subchId); - - if (subchannel == ensemble->subchannels.end()) { - etiLog.log(error, - "Subchannel %i does not exist for component " - "of service %i\n", - (*componentFIG0_13)->subchId, - (*componentFIG0_13)->serviceId); - returnCode = -1; - throw MuxInitException(); - } - - if ( transmitFIG0_13programme && - (*subchannel)->type == Audio && - (*componentFIG0_13)->audio.uaType != 0xffff) { - if (fig0 == NULL) { - fig0 = (FIGtype0*)&etiFrame[index]; - fig0->FIGtypeNumber = 0; - fig0->Length = 1; - fig0->CN = 0; - fig0->OE = 0; - fig0->PD = 0; - fig0->Extension = 13; - index += 2; - figSize += 2; - } - - if (figSize > 30 - (3+4+11)) { - break; - } - - FIG0_13_shortAppInfo* info = - (FIG0_13_shortAppInfo*)&etiFrame[index]; - info->SId = htonl((*componentFIG0_13)->serviceId) >> 16; - info->SCIdS = (*componentFIG0_13)->SCIdS; - info->No = 1; - index += 3; - figSize += 3; - fig0->Length += 3; - - FIG0_13_app* app = (FIG0_13_app*)&etiFrame[index]; - app->setType((*componentFIG0_13)->audio.uaType); - app->length = 4; - app->xpad = htonl(0x0cbc0000); - /* xpad meaning - CA = 0 - CAOrg = 0 - Rfu = 0 - AppTy(5) = 12 (MOT, start of X-PAD data group) - DG = 0 (MSC data groups used) - Rfu = 0 - DSCTy(6) = 60 (MOT) - CAOrg(16) = 0 - */ - - index += 2 + app->length; - figSize += 2 + app->length; - fig0->Length += 2 + app->length; - } - else if (!transmitFIG0_13programme && - (*subchannel)->type == Packet && - (*componentFIG0_13)->packet.appType != 0xffff) { - - if (fig0 == NULL) { - fig0 = (FIGtype0*)&etiFrame[index]; - fig0->FIGtypeNumber = 0; - fig0->Length = 1; - fig0->CN = 0; - fig0->OE = 0; - fig0->PD = 1; - fig0->Extension = 13; - index += 2; - figSize += 2; - } - - if (figSize > 30 - (5+2)) { - break; - } - - FIG0_13_longAppInfo* info = - (FIG0_13_longAppInfo*)&etiFrame[index]; - info->SId = htonl((*componentFIG0_13)->serviceId); - info->SCIdS = (*componentFIG0_13)->SCIdS; - info->No = 1; - index += 5; - figSize += 5; - fig0->Length += 5; - - FIG0_13_app* app = (FIG0_13_app*)&etiFrame[index]; - app->setType((*componentFIG0_13)->packet.appType); - app->length = 0; - index += 2; - figSize += 2; - fig0->Length += 2; - } - } - break; - - case 7: - //Time and country identifier - fig0_10 = (FIGtype0_10 *) & etiFrame[index]; - - fig0_10->FIGtypeNumber = 0; - fig0_10->Length = 5; - fig0_10->CN = 0; - fig0_10->OE = 0; - fig0_10->PD = 0; - fig0_10->Extension = 10; - index = index + 2; - - timeData = gmtime(&date); - - fig0_10->RFU = 0; - fig0_10->setMJD(gregorian2mjd(timeData->tm_year + 1900, - timeData->tm_mon + 1, - timeData->tm_mday)); - fig0_10->LSI = 0; - fig0_10->ConfInd = (watermarkData[watermarkPos >> 3] >> - (7 - (watermarkPos & 0x07))) & 1; - if (++watermarkPos == watermarkSize) { - watermarkPos = 0; - } - fig0_10->UTC = 0; - fig0_10->setHours(timeData->tm_hour); - fig0_10->Minutes = timeData->tm_min; - index = index + 4; - figSize += 6; - - fig0_9 = (FIGtype0_9*)&etiFrame[index]; - fig0_9->FIGtypeNumber = 0; - fig0_9->Length = 4; - fig0_9->CN = 0; - fig0_9->OE = 0; - fig0_9->PD = 0; - fig0_9->Extension = 9; - - fig0_9->ext = 0; - fig0_9->lto = 0; // Unique LTO for ensemble - - if (ensemble->lto_auto) { - time_t now = time(NULL); - struct tm* ltime = localtime(&now); - time_t now2 = timegm(ltime); - ensemble->lto = (now2 - now) / 1800; - } - - if (ensemble->lto >= 0) { - fig0_9->ensembleLto = ensemble->lto; - } - else { - /* Convert to 1-complement representation */ - fig0_9->ensembleLto = (-ensemble->lto) | (1<<5); - } - - fig0_9->ensembleEcc = ensemble->ecc; - fig0_9->tableId = ensemble->international_table; - index += 5; - figSize += 5; - - break; - } - - assert(figSize <= 30); - memcpy(&etiFrame[index], Padding_FIB, 30 - figSize); - index += 30 - figSize; - - CRCtmp = 0xffff; - CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); - CRCtmp ^= 0xffff; - etiFrame[index++] = ((char *) &CRCtmp)[1]; - etiFrame[index++] = ((char *) &CRCtmp)[0]; - - - figSize = 0; - // FIB 2 insertion - if (rotateFIB < ensemble->services.size()) { - service = ensemble->services.begin() + rotateFIB; - - // FIG type 1/1, SI, Service label, one instance per subchannel - if ((*service)->getType(ensemble) == 0) { - fig1_1 = (FIGtype1_1 *) & etiFrame[index]; - - fig1_1->FIGtypeNumber = 1; - fig1_1->Length = 21; - fig1_1->Charset = 0; - fig1_1->OE = 0; - fig1_1->Extension = 1; - - fig1_1->Sld = htons((*service)->id); - index += 4; - figSize += 4; - } - else { - fig1_5 = (FIGtype1_5 *) & etiFrame[index]; - fig1_5->FIGtypeNumber = 1; - fig1_5->Length = 23; - fig1_5->Charset = 0; - fig1_5->OE = 0; - fig1_5->Extension = 5; - - fig1_5->SId = htonl((*service)->id); - index += 6; - figSize += 6; - } - memcpy(&etiFrame[index], (*service)->label.text(), 16); - index += 16; - figSize += 16; - etiFrame[index++] = (*service)->label.flag() >> 8; - etiFrame[index++] = (*service)->label.flag() & 0xFF; - figSize += 2; - } - else if (rotateFIB < - ensemble->services.size() + ensemble->components.size()) { - component = ensemble->components.begin() + - (rotateFIB - ensemble->services.size()); - - service = getService(*component, ensemble->services); - - subchannel = - getSubchannel(ensemble->subchannels, (*component)->subchId); - - if ((*component)->label.text()[0] != 0) { - if ((*service)->getType(ensemble) == 0) { // Programme - FIGtype1_4_programme *fig1_4; - fig1_4 = (FIGtype1_4_programme*)&etiFrame[index]; - - fig1_4->FIGtypeNumber = 1; - fig1_4->Length = 22; - fig1_4->Charset = 0; - fig1_4->OE = 0; - fig1_4->Extension = 4; - fig1_4->PD = 0; - fig1_4->rfa = 0; - fig1_4->SCIdS = (*component)->SCIdS; - - fig1_4->SId = htons((*service)->id); - index += 5; - figSize += 5; - } - else { // Data - FIGtype1_4_data *fig1_4; - fig1_4 = (FIGtype1_4_data *) & etiFrame[index]; - fig1_4->FIGtypeNumber = 1; - fig1_4->Length = 24; - fig1_4->Charset = 0; - fig1_4->OE = 0; - fig1_4->Extension = 4; - fig1_4->PD = 1; - fig1_4->rfa = 0; - fig1_4->SCIdS = (*component)->SCIdS; - - fig1_4->SId = htonl((*service)->id); - index += 7; - figSize += 7; - } - memcpy(&etiFrame[index], (*component)->label.text(), 16); - index += 16; - figSize += 16; - - etiFrame[index++] = (*component)->label.flag() >> 8; - etiFrame[index++] = (*component)->label.flag() & 0xFF; - figSize += 2; - } - } - memcpy(&etiFrame[index], Padding_FIB, 30 - figSize); - index += 30 - figSize; - - CRCtmp = 0xffff; - CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); - CRCtmp ^= 0xffff; - etiFrame[index++] = ((char *) &CRCtmp)[1]; - etiFrame[index++] = ((char *) &CRCtmp)[0]; - - /* ETSI EN 300 799 Table 2: - * Only TM3 has a FIB count to CIF count that is - * not 3 to 1. - */ - if (ensemble->mode == 3) { - memcpy(&etiFrame[index], Padding_FIB, 30); - index += 30; - - CRCtmp = 0xffff; - CRCtmp = crc16(CRCtmp, &etiFrame[(index - 30)], 30); - CRCtmp ^= 0xffff; - etiFrame[index++] = ((char *) &CRCtmp)[1]; - etiFrame[index++] = ((char *) &CRCtmp)[0]; - } - - if (ensemble->services.size() > 30) { - etiLog.log(error, - "Sorry, but this software currently can't write " - "Service Label of more than 30 services.\n"); - returnCode = -1; - throw MuxInitException(); - } - - // counter for FIG 0/0 - insertFIG = (insertFIG + 1) % 16; - - // We rotate through the FIBs every 30 frames - rotateFIB = (rotateFIB + 1) % 30; - - /********************************************************************** - ****** Input Data Reading ******************************************* - **********************************************************************/ - - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - - TagESTn* tag = edi_subchannelToTag[*subchannel]; - - int sizeSubchannel = getSizeByte(*subchannel); - int result = (*subchannel)->input->readFrame( - &etiFrame[index], sizeSubchannel); - - if (result < 0) { - etiLog.log(info, - "Subchannel %d read failed at ETI frame number: %d\n", - (*subchannel)->id, currentFrame); - } - - // save pointer to Audio or Data Stream into correct TagESTn for EDI - tag->mst_data = &etiFrame[index]; - - index += sizeSubchannel; - } - - - index = (3 + fc->NST + FICL) * 4; - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - index += getSizeByte(*subchannel); - } - - /******* Section EOF **************************************************/ - // End of Frame, 4 octets - index = (FLtmp + 1 + 1) * 4; - eti_EOF *eof = (eti_EOF *) & etiFrame[index]; - - // CRC of Main Stream data (MST), 16 bits - index = ((fc->NST) + 2 + 1) * 4; // MST position - MSTsize = ((FLtmp) - 1 - (fc->NST)) * 4; // data size - - CRCtmp = 0xffff; - CRCtmp = crc16(CRCtmp, &etiFrame[index], MSTsize); - CRCtmp ^= 0xffff; - eof->CRC = htons(CRCtmp); - - //RFU, Reserved for future use, 2 bytes, should be 0xFFFF - eof->RFU = htons(0xFFFF); - - /******* Section TIST *************************************************/ - // TimeStamps, 24 bits + 1 octet - index = (FLtmp + 2 + 1) * 4; - eti_TIST *tist = (eti_TIST *) & etiFrame[index]; - - if (enableTist) { - tist->TIST = htonl(timestamp) | 0xff; - } - else { - tist->TIST = htonl(0xffffff) | 0xff; - } - - timestamp += 3 << 17; - if (timestamp > 0xf9ffff) - { - timestamp -= 0xfa0000; - - // Also update MNSC time for next frame - MNSC_increment_time = true; - } - - - - /********************************************************************** - *********** Section FRPD ***************************************** - **********************************************************************/ - - int frame_size = (FLtmp + 1 + 1 + 1 + 1) * 4; - - // Give the data to the outputs - for (output = outputs.begin() ; output != outputs.end(); ++output) { - if ((*output)->output->Write(etiFrame, frame_size) - == -1) { - etiLog.log(error, "Can't write to output %s://%s\n", - (*output)->outputProto.c_str(), (*output)->outputName.c_str()); - } - } - -#ifdef DUMP_BRIDGE - dumpBytes(dumpData, sizeSubChannel, stderr); -#endif // DUMP_BRIDGE - - /********************************************************************** - *********** Finalise and send EDI ******************************** - **********************************************************************/ - - if (edi_conf.enabled) { - // put tags *ptr, DETI and all subchannels into one TagPacket - edi_tagpacket.tag_items.push_back(&edi_tagStarPtr); - edi_tagpacket.tag_items.push_back(&edi_tagDETI); - - list<TagESTn>::iterator tag; - for (tag = edi_subchannels.begin(); tag != edi_subchannels.end(); ++tag) { - edi_tagpacket.tag_items.push_back(&(*tag)); - } - - // Assemble into one AF Packet - AFPacket edi_afpacket = edi_afPacketiser.Assemble(edi_tagpacket); - - if (edi_conf.enable_pft) { - // Apply PFT layer to AF Packet (Reed Solomon FEC and Fragmentation) - vector< PFTFragment > edi_fragments = - edi_pft.Assemble(edi_afpacket); - - // Send over ethernet - vector< vector<uint8_t> >::iterator edi_frag; - for (edi_frag = edi_fragments.begin(); - edi_frag != edi_fragments.end(); - ++edi_frag) { - - UdpPacket udppacket; - - InetAddress& addr = udppacket.getAddress(); - addr.setAddress(edi_conf.dest_addr.c_str()); - addr.setPort(edi_conf.dest_port); - - udppacket.addData(&(edi_frag->front()), edi_frag->size()); - - edi_output.send(udppacket); - - if (edi_conf.dump) { - std::ostream_iterator<uint8_t> debug_iterator(edi_debug_file); - std::copy(edi_frag->begin(), edi_frag->end(), debug_iterator); - } - } - - if (edi_conf.verbose) { - fprintf(stderr, "EDI number of PFT fragments %zu\n", - edi_fragments.size()); - } - } - else { - // Send over ethernet - - UdpPacket udppacket; - - InetAddress& addr = udppacket.getAddress(); - addr.setAddress(edi_conf.dest_addr.c_str()); - addr.setPort(edi_conf.dest_port); - - udppacket.addData(&(edi_afpacket.front()), edi_afpacket.size()); - - edi_output.send(udppacket); - } - - if (edi_conf.dump) { - std::ostream_iterator<uint8_t> debug_iterator(edi_debug_file); - std::copy(edi_afpacket.begin(), edi_afpacket.end(), debug_iterator); - } - } - -#if _DEBUG - /********************************************************************** - *********** Output a small message ********************************* - **********************************************************************/ - if (currentFrame % 100 == 0) { - if (enableTist) { - etiLog.log(info, "ETI frame number %i Timestamp: %d + %f\n", - currentFrame, mnsc_time.tv_sec, - (timestamp & 0xFFFFFF) / 16384000.0); - } - else { - etiLog.log(info, "ETI frame number %i Time: %d, no TIST\n", - currentFrame, mnsc_time.tv_sec); - } } -#endif /* Check every six seconds if the remote control is still working */ - if (rc && fc->FCT == 249 && rc->fault_detected()) { - etiLog.level(warn) << "Detected Remote Control fault, restarting it"; - rc->restart(); + if ((currentFrame % 250 == 249) && rc->fault_detected()) { + etiLog.level(warn) << "Detected Remote Control fault, restarting it"; + rc->restart(); } /* Same for statistics server */ - if (mgmt_server && fc->FCT % 10 == 0) { - if (mgmt_server->fault_detected()) { + if (currentFrame % 10 == 0) { + ManagementServer& mgmt_server = get_mgmt_server(); + + if (mgmt_server.fault_detected()) { etiLog.level(warn) << - "Detected Statistics Server fault, restarting it"; - mgmt_server->restart(); + "Detected Management Server fault, restarting it"; + mgmt_server.restart(); + } + else if (mgmt_server.request_pending()) { + mgmt_server.update_ptree(pt); } - else if (mgmt_server->request_pending()) { - mgmt_server->update_ptree(pt); + else if (mgmt_server.retrieve_new_ptree(pt)) { + etiLog.level(warn) << + "Detected configuration change"; + mux.update_config(pt); } } } - + etiLog.level(info) << "Goodbye"; } catch (const MuxInitException& except) { - etiLog.level(error) << "Caught multiplex initialisation error: " << + etiLog.level(error) << "Multiplex initialisation aborted: " << except.what(); } catch (const std::invalid_argument& except) { @@ -2200,30 +484,7 @@ int main(int argc, char *argv[]) etiLog.log(debug, "exiting...\n"); fflush(stderr); - // close files fichiers - for (subchannel = ensemble->subchannels.begin(); - subchannel != ensemble->subchannels.end(); - ++subchannel) { - if ((*subchannel)->input != NULL) { - (*subchannel)->input->close(); - } - delete (*subchannel)->input; - } - for (output = outputs.begin() ; output != outputs.end(); ++output) { - if ((*output)->output) { - (*output)->output->Close(); - delete ((*output)->output); - } - } - for_each(ensemble->components.begin(), ensemble->components.end(), free); - for_each(ensemble->services.begin(), ensemble->services.end(), free); - for_each(ensemble->subchannels.begin(), ensemble->subchannels.end(), free); - for_each(outputs.begin(), outputs.end(), free); - ensemble->components.clear(); - ensemble->services.clear(); - ensemble->subchannels.clear(); - delete ensemble; outputs.clear(); UdpSocket::clean(); diff --git a/src/DabMux.h b/src/DabMux.h index 89868ac..5dda759 100644 --- a/src/DabMux.h +++ b/src/DabMux.h @@ -31,6 +31,7 @@ #include <stdint.h> #include <string> #include <vector> +#include "DabMultiplexer.h" #include "RemoteControl.h" #include "dabOutput/dabOutput.h" #include "dabInput.h" @@ -43,368 +44,5 @@ # include <sys/time.h> #endif - -// DAB Mode -#define DEFAULT_DAB_MODE 2 - -// Taille de la trame de donnee, sous-canal 3, nb de paquets de 64bits, -// STL3 * 8 = x kbytes par trame ETI - -// Data bitrate in kbits/s. Must be 64 kb/s multiple. -#define DEFAULT_DATA_BITRATE 384 -#define DEFAULT_PACKET_BITRATE 32 - -/* default ensemble parameters. Label must be max 16 chars, short label - * a subset of the label, max 8 chars - */ -#define DEFAULT_ENSEMBLE_LABEL "ODR Dab Mux" -#define DEFAULT_ENSEMBLE_SHORT_LABEL "ODRMux" -#define DEFAULT_ENSEMBLE_ID 0xc000 -#define DEFAULT_ENSEMBLE_ECC 0xa1 - -// start value for default service IDs (if not overridden by configuration) -#define DEFAULT_SERVICE_ID 50 -#define DEFAULT_PACKET_ADDRESS 0 - -/***************************************************************************** - ***************** Definition of FIG structures **************************** - *****************************************************************************/ -struct FIGtype0 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; -} PACKED; - - -struct FIGtype0_0 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; - - uint16_t EId; - uint8_t CIFcnt_hight:5; - uint8_t Al:1; - uint8_t Change:2; - uint8_t CIFcnt_low:8; -} PACKED; - - -struct FIGtype0_2 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; -} PACKED; - - -struct FIGtype0_2_Service { - uint16_t SId; - uint8_t NbServiceComp:4; - uint8_t CAId:3; - uint8_t Local_flag:1; -} PACKED; - - -struct FIGtype0_2_Service_data { - uint32_t SId; - uint8_t NbServiceComp:4; - uint8_t CAId:3; - uint8_t Local_flag:1; -} PACKED; - - -struct FIGtype0_2_audio_component { - uint8_t ASCTy:6; - uint8_t TMid:2; - uint8_t CA_flag:1; - uint8_t PS:1; - uint8_t SubChId:6; -} PACKED; - - -struct FIGtype0_2_data_component { - uint8_t DSCTy:6; - uint8_t TMid:2; - uint8_t CA_flag:1; - uint8_t PS:1; - uint8_t SubChId:6; -} PACKED; - - -struct FIGtype0_2_packet_component { - uint8_t SCId_high:6; - uint8_t TMid:2; - uint8_t CA_flag:1; - uint8_t PS:1; - uint8_t SCId_low:6; - void setSCId(uint16_t SCId) { - SCId_high = SCId >> 6; - SCId_low = SCId & 0x3f; - } -} PACKED; - - -struct FIGtype0_3_header { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; -} PACKED; - - -/* Warning: When bit SCCA_flag is unset(0), the multiplexer R&S does not send - * the SCCA field. But, in the Factum ETI analyzer, if this field is not there, - * it is an error. - */ -struct FIGtype0_3_data { - uint8_t SCId_high; - uint8_t SCCA_flag:1; - uint8_t rfa:3; - uint8_t SCId_low:4; - uint8_t DSCTy:6; - uint8_t rfu:1; - uint8_t DG_flag:1; - uint8_t Packet_address_high:2; - uint8_t SubChId:6; - uint8_t Packet_address_low; - uint16_t SCCA; - void setSCId(uint16_t SCId) { - SCId_high = SCId >> 4; - SCId_low = SCId & 0xf; - } - void setPacketAddress(uint16_t address) { - Packet_address_high = address >> 8; - Packet_address_low = address & 0xff; - } -} PACKED; - - -struct FIGtype0_8_short { - uint8_t SCIdS:4; - uint8_t rfa_1:3; - uint8_t ext:1; - uint8_t Id:6; - uint8_t MscFic:1; - uint8_t LS:1; - uint8_t rfa_2; -} PACKED; - - -struct FIGtype0_8_long { - uint8_t SCIdS:4; - uint8_t rfa_1:3; - uint8_t ext:1; - uint8_t SCId_high:4; - uint8_t rfa:3; - uint8_t LS:1; - uint8_t SCId_low; - uint8_t rfa_2; - void setSCId(uint16_t id) { - SCId_high = id >> 8; - SCId_low = id & 0xff; - } - uint16_t getSCid() { - return (SCId_high << 8) | SCId_low; - } -} PACKED; - - -struct FIGtype0_9 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; - - uint8_t ensembleLto:6; - uint8_t lto:1; - uint8_t ext:1; - uint8_t ensembleEcc; - uint8_t tableId; -} PACKED; - - -struct FIGtype0_10 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; - - uint8_t MJD_high:7; - uint8_t RFU:1; - uint8_t MJD_med; - uint8_t Hours_high:3; - uint8_t UTC:1; - uint8_t ConfInd:1; - uint8_t LSI:1; - uint8_t MJD_low:2; - uint8_t Minutes:6; - uint8_t Hours_low:2; - void setMJD(uint32_t date) { - MJD_high = (date >> 10) & 0x7f; - MJD_med = (date >> 2) & 0xff; - MJD_low = date & 0x03; - } - void setHours(uint16_t hours) { - Hours_high = (hours >> 2) & 0x07; - Hours_low = hours & 0x03; - } -} PACKED; - - -struct FIGtype0_17_programme { - uint16_t SId; - uint8_t NFC:2; - uint8_t Rfa:2; - uint8_t CC:1; - uint8_t L:1; - uint8_t PS:1; - uint8_t SD:1; -} PACKED; - - -struct FIGtype0_1 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:5; - uint8_t PD:1; - uint8_t OE:1; - uint8_t CN:1; -} PACKED; - - -struct FIG_01_SubChannel_ShortF { - uint8_t StartAdress_high:2; - uint8_t SubChId:6; - uint8_t StartAdress_low:8; - uint8_t TableIndex:6; - uint8_t TableSwitch:1; - uint8_t Short_Long_form:1; -} PACKED; - - -struct FIG_01_SubChannel_LongF { - uint8_t StartAdress_high:2; - uint8_t SubChId:6; - uint8_t StartAdress_low:8; - uint8_t Sub_ChannelSize_high:2; - uint8_t ProtectionLevel:2; - uint8_t Option:3; - uint8_t Short_Long_form:1; - uint8_t Sub_ChannelSize_low:8; -} PACKED; - - -// See EN 300 401, Clause 8.1.20 for the FIG0_13 description -struct FIG0_13_shortAppInfo { - uint16_t SId; - uint8_t No:4; - uint8_t SCIdS:4; -} PACKED; - - -struct FIG0_13_longAppInfo { - uint32_t SId; - uint8_t No:4; - uint8_t SCIdS:4; -} PACKED; - - -struct FIG0_13_app { - uint8_t typeHigh; - uint8_t length:5; - uint8_t typeLow:3; - void setType(uint16_t type) { - typeHigh = type >> 3; - typeLow = type & 0x1f; - } - uint32_t xpad; -} PACKED; - -#define FIG0_13_APPTYPE_SLIDESHOW 0x2 -#define FIG0_13_APPTYPE_WEBSITE 0x3 -#define FIG0_13_APPTYPE_TPEG 0x4 -#define FIG0_13_APPTYPE_DGPS 0x5 -#define FIG0_13_APPTYPE_TMC 0x6 -#define FIG0_13_APPTYPE_EPG 0x7 -#define FIG0_13_APPTYPE_DABJAVA 0x8 -#define FIG0_13_APPTYPE_JOURNALINE 0x441 - - -struct FIGtype1_0 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:3; - uint8_t OE:1; - uint8_t Charset:4; - - uint16_t EId; -} PACKED; - - -struct FIGtype1_1 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:3; - uint8_t OE:1; - uint8_t Charset:4; - - uint16_t Sld; -} PACKED; - - -struct FIGtype1_5 { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:3; - uint8_t OE:1; - uint8_t Charset:4; - uint32_t SId; -} PACKED; - - -struct FIGtype1_4_programme { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:3; - uint8_t OE:1; - uint8_t Charset:4; - uint8_t SCIdS:4; - uint8_t rfa:3; - uint8_t PD:1; - uint16_t SId; -} PACKED; - - -struct FIGtype1_4_data { - uint8_t Length:5; - uint8_t FIGtypeNumber:3; - uint8_t Extension:3; - uint8_t OE:1; - uint8_t Charset:4; - uint8_t SCIdS:4; - uint8_t rfa:3; - uint8_t PD:1; - uint32_t SId; -} PACKED; - - -#ifdef _WIN32 -# pragma pack(pop) -#endif - #endif @@ -73,10 +73,12 @@ class LogToSyslog : public LogBackend { int syslog_level = LOG_EMERG; switch (level) { case debug: syslog_level = LOG_DEBUG; break; - case alert: syslog_level = LOG_ALERT; break; case info: syslog_level = LOG_INFO; break; + /* we don't have the notice level */ case warn: syslog_level = LOG_WARNING; break; case error: syslog_level = LOG_ERR; break; + default: syslog_level = LOG_CRIT; break; + case alert: syslog_level = LOG_ALERT; break; case emerg: syslog_level = LOG_EMERG; break; } diff --git a/src/Makefile.am b/src/Makefile.am index f3bce5e..0e55b52 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,7 +21,7 @@ # along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. if IS_GIT_REPO -GITVERSION_FLAGS = -DGITVERSION="\"`git describe`\"" +GITVERSION_FLAGS = -DGITVERSION="\"`git describe --dirty`\"" else GITVERSION_FLAGS = endif @@ -39,9 +39,11 @@ bin_PROGRAMS=odr-dabmux odr-bridgetest ZMQ_LIBS = endif -odr_dabmux_CPPFLAGS =-Wall -I$(FARSYNC_DIR) $(GITVERSION_FLAGS) +odr_dabmux_CFLAGS =-Wall -I$(FARSYNC_DIR) $(GITVERSION_FLAGS) +odr_dabmux_CXXFLAGS =-Wall -I$(FARSYNC_DIR) $(GITVERSION_FLAGS) odr_dabmux_LDADD =$(FEC_LIBS) $(ZMQ_LIBS) -lpthread -lboost_thread -lboost_system odr_dabmux_SOURCES =DabMux.cpp DabMux.h \ + DabMultiplexer.cpp DabMultiplexer.h \ dabInput.h dabInput.cpp \ dabInputBridgeUdp.h dabInputBridgeUdp.cpp \ dabInputDabplusFifo.h dabInputDabplusFifo.cpp \ diff --git a/src/ManagementServer.cpp b/src/ManagementServer.cpp index 9ebdfeb..9687278 100644 --- a/src/ManagementServer.cpp +++ b/src/ManagementServer.cpp @@ -39,6 +39,18 @@ #include "ManagementServer.h" #include "Log.h" +ManagementServer& get_mgmt_server() +{ + static ManagementServer mgmt_server; + + return mgmt_server; + + /* Warning, do not use the mgmt_server in the destructor + * of another global object: you don't know which one + * gets destroyed first + */ +} + void ManagementServer::registerInput(InputStat* is) { boost::mutex::scoped_lock lock(m_statsmutex); @@ -47,7 +59,7 @@ void ManagementServer::registerInput(InputStat* is) if (m_inputStats.count(id) == 1) { etiLog.level(error) << - "Double registration in Stats Server with id '" << + "Double registration in MGMT Server with id '" << id << "'"; return; } @@ -71,7 +83,7 @@ bool ManagementServer::isInputRegistered(std::string& id) if (m_inputStats.count(id) == 0) { etiLog.level(error) << - "Stats Server id '" << + "Management Server: id '" << id << "' does was not registered"; return false; } @@ -185,132 +197,139 @@ void ManagementServer::restart_thread(long) void ManagementServer::serverThread() { + m_running = true; m_fault = false; - try { - int accepted_sock; - char buffer[256]; - char welcome_msg[256]; - struct sockaddr_in serv_addr, cli_addr; - int n; - - int welcome_msg_len = snprintf(welcome_msg, 256, - "{ \"service\": \"" - "%s %s Stats Server\" }\n", - PACKAGE_NAME, -#if defined(GITVERSION) - GITVERSION -#else - PACKAGE_VERSION -#endif - ); - + std::stringstream bind_addr; + bind_addr << "tcp://127.0.0.1:" << m_listenport; + m_zmq_sock.bind(bind_addr.str().c_str()); - m_sock = socket(AF_INET, SOCK_STREAM, 0); - if (m_sock < 0) { - etiLog.level(error) << "Error opening Stats Server socket: " << - strerror(errno); - m_fault = true; - return; - } + while (m_running) { + zmq::message_t zmq_message; + m_zmq_sock.recv(&zmq_message); - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = INADDR_ANY; // TODO listen only on 127.0.0.1 - serv_addr.sin_port = htons(m_listenport); - if (bind(m_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { - etiLog.level(error) << "Error binding Stats Server socket: " << - strerror(errno); - goto end_serverthread; - } + handle_message(zmq_message); + } - if (listen(m_sock, 5) < 0) { - etiLog.level(error) << "Error listening on Stats Server socket: " << - strerror(errno); - goto end_serverthread; - } + m_fault = true; +} - m_running = true; +bool ManagementServer::handle_setptree( + zmq::message_t& zmq_message, std::stringstream& answer) +{ + try { + if (zmq_message.more()) { + zmq::message_t zmq_new_ptree; + m_zmq_sock.recv(&zmq_new_ptree); + std::string new_ptree( + (char*)zmq_new_ptree.data(), zmq_new_ptree.size() + ); - while (m_running) { - socklen_t cli_addr_len = sizeof(cli_addr); + etiLog.level(info) << "Received ptree " << new_ptree; - /* Accept actual connection from the client */ - accepted_sock = accept(m_sock, - (struct sockaddr *)&cli_addr, - &cli_addr_len); + boost::unique_lock<boost::mutex> lock(m_configmutex); + m_pt.clear(); - if (accepted_sock < 0) { - etiLog.level(warn) << "Stats Server cound not accept connection: " << - strerror(errno); - continue; - } - /* Send welcome message with version */ - n = write(accepted_sock, welcome_msg, welcome_msg_len); - if (n < 0) { - etiLog.level(warn) << "Error writing to Stats Server socket " << - strerror(errno); - close(accepted_sock); - continue; - } + std::stringstream json_stream; + json_stream << new_ptree; + boost::property_tree::json_parser::read_json(json_stream, m_pt); - /* receive command */ - memset(buffer, 0, 256); - int n = read(accepted_sock, buffer, 255); - if (n < 0) { - etiLog.level(warn) << "Error reading from Stats Server socket " << - strerror(errno); - close(accepted_sock); - continue; - } + m_retrieve_pending = true; + answer << "OK"; - if (strcmp(buffer, "config\n") == 0) { - std::string json = getStatConfigJSON(); - n = write(accepted_sock, json.c_str(), json.size()); - } - else if (strcmp(buffer, "values\n") == 0) { - std::string json = getValuesJSON(); - n = write(accepted_sock, json.c_str(), json.size()); - } - else if (strcmp(buffer, "state\n") == 0) { - std::string json = getStateJSON(); - n = write(accepted_sock, json.c_str(), json.size()); - } - if (strcmp(buffer, "getptree\n") == 0) { - boost::unique_lock<boost::mutex> lock(m_configmutex); - m_pending = true; + return true; + } + else { + etiLog.level(error) << + "MGMT: setptree command is missing data."; + } + } + catch (std::exception& e) { + etiLog.level(error) << + "MGMT: setptree error." << e.what(); + } + return false; +} - while (m_pending) { - m_condition.wait(lock); - } - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, m_pt); +void ManagementServer::handle_message(zmq::message_t& zmq_message) +{ + std::stringstream answer; + std::string data((char*)zmq_message.data(), zmq_message.size()); - std::string response = ss.str(); + try { + etiLog.level(info) << "RC: Accepted"; - n = write(accepted_sock, response.c_str(), response.size()); - } - else { - int len = snprintf(buffer, 256, "Invalid command\n"); - n = write(accepted_sock, buffer, len); - } + if (data == "info") { + answer << + "{ \"service\": \"" << + PACKAGE_NAME << " " << +#if defined(GITVERSION) + GITVERSION << +#else + PACKAGE_VERSION << +#endif + " MGMT Server\" }\n"; + } + else if (data == "config") { + answer << getStatConfigJSON(); + } + else if (data == "values") { + answer << getValuesJSON(); + } + else if (data == "state") { + answer << getStateJSON(); + } + else if (data == "setptree") { + handle_setptree(zmq_message, answer); + } + else if (data == "getptree") { + boost::unique_lock<boost::mutex> lock(m_configmutex); + m_pending = true; - if (n < 0) { - etiLog.level(warn) << "Error writing to Stats Server socket " << - strerror(errno); + while (m_pending && !m_retrieve_pending) { + m_condition.wait(lock); } - close(accepted_sock); + boost::property_tree::json_parser::write_json(answer, m_pt); + } + else { + answer << "Invalid command"; } -end_serverthread: - m_fault = true; - close(m_sock); - + std::string answerstr(answer.str()); + m_zmq_sock.send(answerstr.c_str(), answerstr.size()); } catch (std::exception& e) { - etiLog.level(error) << "Statistics server caught exception: " << e.what(); - m_fault = true; + etiLog.level(error) << + "MGMT server caught exception: " << + e.what(); + } +} + +bool ManagementServer::retrieve_new_ptree(boost::property_tree::ptree& pt) +{ + boost::unique_lock<boost::mutex> lock(m_configmutex); + + if (m_retrieve_pending) + { + pt = m_pt; + + m_retrieve_pending = false; + m_condition.notify_one(); + return true; + } + + return false; +} + +void ManagementServer::update_ptree(const boost::property_tree::ptree& pt) +{ + if (m_running) { + boost::unique_lock<boost::mutex> lock(m_configmutex); + m_pt = pt; + m_pending = false; + + m_condition.notify_one(); } } @@ -318,12 +337,12 @@ end_serverthread: void InputStat::registerAtServer() { - mgmt_server->registerInput(this); + get_mgmt_server().registerInput(this); } InputStat::~InputStat() { - mgmt_server->unregisterInput(m_name); + get_mgmt_server().unregisterInput(m_name); } std::string InputStat::encodeValuesJSON() diff --git a/src/ManagementServer.h b/src/ManagementServer.h index fa3f170..836dee4 100644 --- a/src/ManagementServer.h +++ b/src/ManagementServer.h @@ -7,7 +7,7 @@ http://www.opendigitalradio.org - A TCP Socket server that serves state information and statistics for + A server that serves state information and statistics for monitoring purposes, and also serves the internal configuration property tree. @@ -15,7 +15,7 @@ http://munin-monitoring.org/ but is not specific to it. - The TCP Server responds in JSON, and accepts the commands: + The responds in JSON, and accepts the commands: - config - values Inspired by the munin equivalent @@ -27,6 +27,7 @@ Returns the internal boost property_tree that contains the multiplexer configuration DB. + The server is using REQ/REP ZeroMQ sockets. */ /* This file is part of ODR-DabMux. @@ -52,15 +53,12 @@ # include "config.h" #endif -#include <sys/socket.h> -#include <netinet/in.h> -#include <unistd.h> -#include <netdb.h> -#include <arpa/inet.h> -#include <pthread.h> +#include "zmq.hpp" #include <string> #include <map> +#include <atomic> #include <boost/thread.hpp> +#include <boost/bind.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/json_parser.hpp> #include <ctime> @@ -317,33 +315,30 @@ class ManagementServer { public: ManagementServer() : - m_listenport(0), + m_zmq_context(), + m_zmq_sock(m_zmq_context, ZMQ_REP), m_running(false), m_fault(false), - m_pending(false) - { } - - ManagementServer(int listen_port) : - m_listenport(listen_port), - m_running(false), - m_fault(false), - m_thread(&ManagementServer::serverThread, this), - m_pending(false) - { - m_sock = 0; - } + m_pending(false) { } ~ManagementServer() { m_running = false; m_fault = false; m_pending = false; - if (m_sock) { - close(m_sock); - } + + // TODO notify m_thread.join(); } + void open(int listenport) + { + m_listenport = listenport; + if (m_listenport > 0) { + m_thread = boost::thread(&ManagementServer::serverThread, this); + } + } + /* Un-/Register a statistics data source */ void registerInput(InputStat* is); void unregisterInput(std::string id); @@ -351,18 +346,16 @@ class ManagementServer /* Ask if there is a configuration request pending */ bool request_pending() { return m_pending; } + /* Load a ptree given by the management server. + * + * Returns true if the ptree was updated + */ + bool retrieve_new_ptree(boost::property_tree::ptree& pt); + /* Update the copy of the configuration property tree and notify the * update to the internal server thread. */ - void update_ptree(const boost::property_tree::ptree& pt) { - if (m_running) { - boost::unique_lock<boost::mutex> lock(m_configmutex); - m_pt = pt; - m_pending = false; - - m_condition.notify_one(); - } - } + void update_ptree(const boost::property_tree::ptree& pt); bool fault_detected() { return m_fault; } void restart(void); @@ -370,24 +363,27 @@ class ManagementServer private: void restart_thread(long); - /******* TCP Socket Server ******/ + /******* Server ******/ + zmq::context_t m_zmq_context; + zmq::socket_t m_zmq_sock; + // no copying (because of the thread) ManagementServer(const ManagementServer& other); void serverThread(void); + void handle_message(zmq::message_t& zmq_message); + bool handle_setptree(zmq::message_t& zmq_message, std::stringstream& answer); bool isInputRegistered(std::string& id); int m_listenport; // serverThread runs in a separate thread - bool m_running; - bool m_fault; + std::atomic<bool> m_running; + std::atomic<bool> m_fault; boost::thread m_thread; boost::thread m_restarter_thread; - int m_sock; - /******* Statistics Data ********/ std::map<std::string, InputStat*> m_inputStats; @@ -415,13 +411,16 @@ class ManagementServer /******** Configuration Data *******/ bool m_pending; + bool m_retrieve_pending; boost::condition_variable m_condition; mutable boost::mutex m_configmutex; boost::property_tree::ptree m_pt; }; -extern ManagementServer* mgmt_server; +// If necessary construct the management server singleton and return +// a reference to it +ManagementServer& get_mgmt_server(); #endif diff --git a/src/MuxElements.cpp b/src/MuxElements.cpp index e8871d1..4bd82cd 100644 --- a/src/MuxElements.cpp +++ b/src/MuxElements.cpp @@ -24,6 +24,7 @@ */ #include <vector> +#include <algorithm> #include "MuxElements.h" #include <boost/algorithm/string.hpp> @@ -43,29 +44,26 @@ const unsigned short Sub_Channel_SizeTable[64] = { using namespace std; -int DabLabel::setLabel(const std::string& text) +int DabLabel::setLabel(const std::string& label) { - int len = text.length(); - if (len > 16) + size_t len = label.length(); + if (len > DABLABEL_LENGTH) return -3; - memset(m_text, 0, 17); - memcpy(m_text, text.c_str(), len); - m_flag = 0xFF00; // truncate the label to the eight first characters + m_label = label; + return 0; } -int DabLabel::setLabel(const std::string& text, const std::string& short_label) +int DabLabel::setLabel(const std::string& label, const std::string& short_label) { DabLabel newlabel; - memset(newlabel.m_text, 0, 17); - int len = text.length(); - if (len > 16) - return -3; - memcpy(newlabel.m_text, text.c_str(), len); + int result = newlabel.setLabel(label); + if (result < 0) + return result; /* First check if we can actually create the short label */ int flag = newlabel.setShortLabel(short_label); @@ -73,8 +71,8 @@ int DabLabel::setLabel(const std::string& text, const std::string& short_label) return flag; // short label is valid. - memcpy(this->m_text, newlabel.m_text, 17); - this->m_flag = flag & 0xFFFF; + m_flag = flag & 0xFFFF; + m_label = newlabel.m_label; return 0; } @@ -104,8 +102,8 @@ int DabLabel::setShortLabel(const std::string& slabel) /* Iterate over the label and set the bits in the flag * according to the characters in the slabel */ - for (int i = 0; i < 32; ++i) { - if (*slab == this->m_text[i]) { + for (size_t i = 0; i < m_label.size(); ++i) { + if (*slab == m_label[i]) { flag |= 0x8000 >> i; if (*(++slab) == 0) { break; @@ -117,7 +115,7 @@ int DabLabel::setShortLabel(const std::string& slabel) * we went through the whole label, the short label * cannot be represented */ - if (*slab != 0) { + if (*slab != '\0') { return -1; } @@ -138,15 +136,22 @@ int DabLabel::setShortLabel(const std::string& slabel) const string DabLabel::short_label() const { stringstream shortlabel; - for (int i = 0; i < 32; ++i) { + for (size_t i = 0; i < m_label.size(); ++i) { if (m_flag & 0x8000 >> i) { - shortlabel << m_text[i]; + shortlabel << m_label[i]; } } return shortlabel.str(); } +void DabLabel::writeLabel(uint8_t* buf) const +{ + memset(buf, ' ', DABLABEL_LENGTH); + if (m_label.size() <= DABLABEL_LENGTH) { + std::copy(m_label.begin(), m_label.end(), (char*)buf); + } +} vector<dabSubchannel*>::iterator getSubchannel( vector<dabSubchannel*>& subchannels, int id) @@ -186,19 +191,19 @@ vector<DabComponent*>::iterator getComponent( return getComponent(components, serviceId, components.end()); } -vector<DabService*>::iterator getService( +std::vector<std::shared_ptr<DabService> >::iterator getService( DabComponent* component, - vector<DabService*>& services) + std::vector<std::shared_ptr<DabService> >& services) { - vector<DabService*>::iterator service; - - for (service = services.begin(); service != services.end(); ++service) { - if ((*service)->id == component->serviceId) { - break; + size_t i = 0; + for (auto service : services) { + if (service->id == component->serviceId) { + return services.begin() + i; } + i++; } - return service; + throw std::runtime_error("Service not included in any component"); } bool DabComponent::isPacketComponent(vector<dabSubchannel*>& subchannels) @@ -224,9 +229,6 @@ bool DabComponent::isPacketComponent(vector<dabSubchannel*>& subchannels) void DabComponent::set_parameter(const string& parameter, const string& value) { - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - if (parameter == "label") { vector<string> fields; boost::split(fields, value, boost::is_any_of(",")); @@ -274,10 +276,7 @@ const string DabComponent::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "label") { - char l[17]; - l[16] = '\0'; - memcpy(l, label.text(), 16); - ss << l << "," << label.short_label(); + ss << label.long_label() << "," << label.short_label(); } else { ss << "Parameter '" << parameter << @@ -289,7 +288,7 @@ const string DabComponent::get_parameter(const string& parameter) const } -unsigned char DabService::getType(dabEnsemble* ensemble) +unsigned char DabService::getType(boost::shared_ptr<dabEnsemble> ensemble) { vector<dabSubchannel*>::iterator subchannel; vector<DabComponent*>::iterator component = @@ -322,9 +321,6 @@ unsigned char DabService::nbComponent(vector<DabComponent*>& components) void DabService::set_parameter(const string& parameter, const string& value) { - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - if (parameter == "label") { vector<string> fields; boost::split(fields, value, boost::is_any_of(",")); @@ -372,10 +368,7 @@ const string DabService::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "label") { - char l[17]; - l[16] = '\0'; - memcpy(l, label.text(), 16); - ss << l << "," << label.short_label(); + ss << label.long_label() << "," << label.short_label(); } else { ss << "Parameter '" << parameter << @@ -388,9 +381,6 @@ const string DabService::get_parameter(const string& parameter) const void dabEnsemble::set_parameter(const string& parameter, const string& value) { - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - if (parameter == "localtimeoffset") { if (value == "auto") { lto_auto = true; diff --git a/src/MuxElements.h b/src/MuxElements.h index 3653ea4..ada7ce3 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li This file defines all data structures used in DabMux to represent @@ -29,6 +29,7 @@ #define _MUX_ELEMENTS #include <vector> +#include <memory> #include <string> #include <functional> #include <algorithm> @@ -51,6 +52,7 @@ struct dabOutput { DabOutput* output; }; +#define DABLABEL_LENGTH 16 class DabLabel { @@ -61,7 +63,7 @@ class DabLabel * -2 if the short_label is too long * -3 if the text is too long */ - int setLabel(const std::string& text, const std::string& short_label); + int setLabel(const std::string& label, const std::string& short_label); /* Same as above, but sets the flag to 0xff00, truncating at 8 * characters. @@ -69,17 +71,28 @@ class DabLabel * returns: 0 on success * -3 if the text is too long */ - int setLabel(const std::string& text); + int setLabel(const std::string& label); + + /* Write the label to the 16-byte buffer given in buf + * In the DAB standard, the label is 16 bytes long, and is + * padded using spaces. + */ + void writeLabel(uint8_t* buf) const; - const char* text() const { return m_text; } uint16_t flag() const { return m_flag; } + const std::string long_label() const { return m_label; } const std::string short_label() const; private: - // In the DAB standard, the label is 16 chars. - // We keep it here zero-terminated - char m_text[17]; + /* The flag field selects which label characters make + * up the short label + */ uint16_t m_flag; + + /* The m_label is not padded in any way */ + std::string m_label; + + /* Checks and calculates the flag */ int setShortLabel(const std::string& slabel); }; @@ -118,7 +131,7 @@ class dabEnsemble : public RemoteControllable { int international_table; - std::vector<DabService*> services; + std::vector<std::shared_ptr<DabService> > services; std::vector<DabComponent*> components; std::vector<dabSubchannel*> subchannels; }; @@ -166,7 +179,16 @@ enum dab_subchannel_type_t { Packet = 3 }; -struct dabSubchannel { +class dabSubchannel +{ +public: + dabSubchannel(std::string& uid) : + uid(uid) + { + } + + std::string uid; + std::string inputUri; DabInputBase* input; unsigned char id; @@ -220,12 +242,15 @@ struct dabPacketComponent { class DabComponent : public RemoteControllable { public: - DabComponent(std::string uid) : - RemoteControllable(uid) + DabComponent(std::string& uid) : + RemoteControllable(uid), + uid(uid) { RC_ADD_PARAMETER(label, "Label and shortlabel [label,short]"); } + std::string uid; + DabLabel label; uint32_t serviceId; uint8_t subchId; @@ -258,18 +283,21 @@ class DabComponent : public RemoteControllable class DabService : public RemoteControllable { public: - DabService(std::string uid) : - RemoteControllable(uid) + DabService(std::string& uid) : + RemoteControllable(uid), + uid(uid) { RC_ADD_PARAMETER(label, "Label and shortlabel [label,short]"); } + std::string uid; + uint32_t id; unsigned char pty; unsigned char language; bool program; - unsigned char getType(dabEnsemble* ensemble); + unsigned char getType(boost::shared_ptr<dabEnsemble> ensemble); unsigned char nbComponent(std::vector<DabComponent*>& components); DabLabel label; @@ -300,9 +328,9 @@ std::vector<DabComponent*>::iterator getComponent( std::vector<DabComponent*>& components, uint32_t serviceId); -std::vector<DabService*>::iterator getService( +std::vector<std::shared_ptr<DabService> >::iterator getService( DabComponent* component, - std::vector<DabService*>& services); + std::vector<std::shared_ptr<DabService> >& services); unsigned short getSizeCu(dabSubchannel* subchannel); @@ -312,5 +340,5 @@ unsigned short getSizeByte(dabSubchannel* subchannel); unsigned short getSizeWord(dabSubchannel* subchannel); - #endif + diff --git a/src/RemoteControl.cpp b/src/RemoteControl.cpp index e46bc8d..723ba9b 100644 --- a/src/RemoteControl.cpp +++ b/src/RemoteControl.cpp @@ -27,6 +27,7 @@ #include <iostream> #include <string> #include <boost/asio.hpp> +#include <boost/bind.hpp> #include <boost/thread.hpp> #include "Log.h" @@ -35,6 +36,12 @@ using boost::asio::ip::tcp; using namespace std; +RemoteControllerTelnet::~RemoteControllerTelnet() +{ + m_running = false; + m_io_service.stop(); + m_child_thread.join(); +} void RemoteControllerTelnet::restart() { @@ -47,85 +54,120 @@ void RemoteControllerTelnet::restart() // thread. void RemoteControllerTelnet::restart_thread(long) { + etiLog.level(warn) << "RC: Restart Telnet server"; + m_running = false; + m_io_service.stop(); - if (m_port) { - m_child_thread.interrupt(); - m_child_thread.join(); - } + m_child_thread.join(); m_child_thread = boost::thread(&RemoteControllerTelnet::process, this, 0); } -void RemoteControllerTelnet::process(long) +void RemoteControllerTelnet::handle_accept( + const boost::system::error_code& boost_error, + boost::shared_ptr< boost::asio::ip::tcp::socket > socket, + boost::asio::ip::tcp::acceptor& acceptor) { - m_welcome = "ODR-DabMux Remote Control CLI\nWrite 'help' for help.\n**********\n"; - m_prompt = "> "; + + const std::string welcome = "ODR-DabMux Remote Control CLI\n" + "Write 'help' for help.\n" + "**********\n"; + const std::string prompt = "> "; std::string in_message; size_t length; - try { - boost::asio::io_service io_service; - tcp::acceptor acceptor(io_service, tcp::endpoint( - boost::asio::ip::address::from_string("127.0.0.1"), m_port) ); - - while (m_running) { - in_message = ""; + if (boost_error) + { + etiLog.level(error) << "RC: Error accepting connection"; + return; + } - tcp::socket socket(io_service); + try { + etiLog.level(info) << "RC: Accepted"; - acceptor.accept(socket); + boost::system::error_code ignored_error; - boost::system::error_code ignored_error; + boost::asio::write(*socket, boost::asio::buffer(welcome), + boost::asio::transfer_all(), + ignored_error); - boost::asio::write(socket, boost::asio::buffer(m_welcome), + while (m_running && in_message != "quit") { + boost::asio::write(*socket, boost::asio::buffer(prompt), boost::asio::transfer_all(), ignored_error); - while (m_running && in_message != "quit") { - boost::asio::write(socket, boost::asio::buffer(m_prompt), - boost::asio::transfer_all(), - ignored_error); - - in_message = ""; + in_message = ""; - boost::asio::streambuf buffer; - length = boost::asio::read_until( socket, buffer, "\n", ignored_error); + boost::asio::streambuf buffer; + length = boost::asio::read_until(*socket, buffer, "\n", ignored_error); - std::istream str(&buffer); - std::getline(str, in_message); + std::istream str(&buffer); + std::getline(str, in_message); - if (length == 0) { - etiLog.level(info) << "RC: Connection terminated"; - break; - } + if (length == 0) { + etiLog.level(info) << "RC: Connection terminated"; + break; + } - while (in_message.length() > 0 && - (in_message[in_message.length()-1] == '\r' || - in_message[in_message.length()-1] == '\n')) { - in_message.erase(in_message.length()-1, 1); - } + while (in_message.length() > 0 && + (in_message[in_message.length()-1] == '\r' || + in_message[in_message.length()-1] == '\n')) { + in_message.erase(in_message.length()-1, 1); + } - if (in_message.length() == 0) { - continue; - } + if (in_message.length() == 0) { + continue; + } - etiLog.level(info) << "RC: Got message '" << in_message << "'"; + etiLog.level(info) << "RC: Got message '" << in_message << "'"; - dispatch_command(socket, in_message); - } - etiLog.level(info) << "RC: Closing socket"; - socket.close(); + dispatch_command(*socket, in_message); } + etiLog.level(info) << "RC: Closing socket"; + socket->close(); } catch (std::exception& e) { etiLog.level(error) << "Remote control caught exception: " << e.what(); - m_fault = true; } } +void RemoteControllerTelnet::process(long) +{ + m_running = true; + + while (m_running) { + m_io_service.reset(); + + tcp::acceptor acceptor(m_io_service, tcp::endpoint( + boost::asio::ip::address::from_string("127.0.0.1"), m_port) ); + + + // Add a job to start accepting connections. + boost::shared_ptr<tcp::socket> socket( + new tcp::socket(acceptor.get_io_service())); + + // Add an accept call to the service. This will prevent io_service::run() + // from returning. + etiLog.level(warn) << "RC: Waiting on connection"; + acceptor.async_accept(*socket, + boost::bind(&RemoteControllerTelnet::handle_accept, + this, + boost::asio::placeholders::error, + socket, + boost::ref(acceptor))); + + // Process event loop. + m_io_service.run(); + } + + etiLog.level(warn) << "RC: Leaving"; + m_fault = true; +} + + void RemoteControllerTelnet::dispatch_command(tcp::socket& socket, string command) { vector<string> cmd = tokenise_(command); diff --git a/src/RemoteControl.h b/src/RemoteControl.h index 16881b4..46a828f 100644 --- a/src/RemoteControl.h +++ b/src/RemoteControl.h @@ -32,6 +32,7 @@ #include <list> #include <map> #include <string> +#include <atomic> #include <iostream> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> @@ -104,6 +105,10 @@ class RemoteControllable { controller.enrol(this); } + virtual void enrol_at(boost::shared_ptr<BaseRemoteController> controller) { + controller->enrol(this); + } + /* Return a list of possible parameters that can be set */ virtual std::list<std::string> get_supported_parameters() const { std::list<std::string> parameterlist; @@ -137,25 +142,23 @@ class RemoteControllable { */ class RemoteControllerTelnet : public BaseRemoteController { public: - RemoteControllerTelnet() - : m_running(false), m_fault(false), + RemoteControllerTelnet() : + m_running(false), + m_io_service(), + m_fault(false), m_port(0) { } - RemoteControllerTelnet(int port) - : m_running(true), m_fault(false), - m_child_thread(&RemoteControllerTelnet::process, this, 0), + RemoteControllerTelnet(int port) : + m_running(false), + m_io_service(), + m_fault(false), m_port(port) - { } - - ~RemoteControllerTelnet() { - m_running = false; - m_fault = false; - if (m_port) { - m_child_thread.interrupt(); - m_child_thread.join(); - } + { + restart(); } + ~RemoteControllerTelnet(); + void enrol(RemoteControllable* controllable) { m_cohort.push_back(controllable); } @@ -174,6 +177,11 @@ class RemoteControllerTelnet : public BaseRemoteController { void reply(boost::asio::ip::tcp::socket& socket, std::string message); + void handle_accept( + const boost::system::error_code& boost_error, + boost::shared_ptr< boost::asio::ip::tcp::socket > socket, + boost::asio::ip::tcp::acceptor& acceptor); + RemoteControllerTelnet& operator=(const RemoteControllerTelnet& other); RemoteControllerTelnet(const RemoteControllerTelnet& other); @@ -237,10 +245,12 @@ class RemoteControllerTelnet : public BaseRemoteController { return controllable->set_parameter(param, value); } - bool m_running; + std::atomic<bool> m_running; + + boost::asio::io_service m_io_service; /* This is set to true if a fault occurred */ - bool m_fault; + std::atomic<bool> m_fault; boost::thread m_restarter_thread; boost::thread m_child_thread; @@ -248,9 +258,6 @@ class RemoteControllerTelnet : public BaseRemoteController { /* This controller commands the controllables in the cohort */ std::list<RemoteControllable*> m_cohort; - std::string m_welcome; - std::string m_prompt; - int m_port; }; diff --git a/src/TcpSocket.cpp b/src/TcpSocket.cpp index 89fefd2..75b320f 100644 --- a/src/TcpSocket.cpp +++ b/src/TcpSocket.cpp @@ -266,7 +266,7 @@ int TcpSocket::write(const void* data, int size) // ignore BROKENPIPE signal (we handle it instead) // void* old_sigpipe = signal ( SIGPIPE, SIG_IGN ); // try to send data - int ret = send(listenSocket, (char*)data, size, 0 /*MSG_NOSIGNAL*/ ); + int ret = send(listenSocket, (const char*)data, size, 0 /*MSG_NOSIGNAL*/ ); // restore the BROKENPIPE handling // signal ( SIGPIPE, (__sighandler_t)old_sigpipe ); if (ret == SOCKET_ERROR) { @@ -236,7 +236,7 @@ void init_crc32tab(uint32_t l_code, uint32_t l_init) uint8_t crc8(uint8_t l_crc, const void *lp_data, unsigned l_nb) { - uint8_t* data = (uint8_t*)lp_data; + const uint8_t* data = (const uint8_t*)lp_data; while (l_nb--) { l_crc = crc8tab[l_crc ^ *(data++)]; } @@ -246,7 +246,7 @@ uint8_t crc8(uint8_t l_crc, const void *lp_data, unsigned l_nb) uint16_t crc16(uint16_t l_crc, const void *lp_data, unsigned l_nb) { - uint8_t* data = (uint8_t*)lp_data; + const uint8_t* data = (const uint8_t*)lp_data; while (l_nb--) { l_crc = (l_crc << 8) ^ crc16tab[(l_crc >> 8) ^ *(data++)]; @@ -257,7 +257,7 @@ uint16_t crc16(uint16_t l_crc, const void *lp_data, unsigned l_nb) uint32_t crc32(uint32_t l_crc, const void *lp_data, unsigned l_nb) { - uint8_t* data = (uint8_t*)lp_data; + const uint8_t* data = (const uint8_t*)lp_data; while (l_nb--) { l_crc = (l_crc << 8) ^ crc32tab[((l_crc >> 24) ^ *(data++)) & 0xff]; diff --git a/src/dabInputZmq.cpp b/src/dabInputZmq.cpp index 9d39945..5c20baf 100644 --- a/src/dabInputZmq.cpp +++ b/src/dabInputZmq.cpp @@ -386,9 +386,9 @@ int DabInputZmqMPEG::readFromSocket(size_t framesize) } else if (m_enable_input) { // copy the input frame blockwise into the frame_buffer - uint8_t* frame = new uint8_t[framesize]; - memcpy(frame, data, framesize); - m_frame_buffer.push_back(frame); + uint8_t* framedata = new uint8_t[framesize]; + memcpy(framedata, data, framesize); + m_frame_buffer.push_back(framedata); } else { return 0; @@ -397,7 +397,7 @@ int DabInputZmqMPEG::readFromSocket(size_t framesize) else { etiLog.level(error) << "inputZMQ " << m_name << - " wrong data size: recv'd " << msg.size() << + " wrong data size: recv'd " << msg.size() << " Bytes" << ", need " << framesize << "."; messageReceived = false; } @@ -498,9 +498,6 @@ int DabInputZmqAAC::readFromSocket(size_t framesize) void DabInputZmqBase::set_parameter(const string& parameter, const string& value) { - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - if (parameter == "buffer") { size_t new_limit = atol(value.c_str()); diff --git a/src/dabOutput/dabOutput.h b/src/dabOutput/dabOutput.h index 18f3848..f6980fe 100644 --- a/src/dabOutput/dabOutput.h +++ b/src/dabOutput/dabOutput.h @@ -78,6 +78,8 @@ class DabOutput virtual int Close() = 0; virtual ~DabOutput() {} + + virtual std::string get_info() = 0; }; // ----- used in File and Fifo outputs @@ -111,7 +113,12 @@ class DabOutputFile : public DabOutput int Write(void* buffer, int size); int Close(); + std::string get_info() { + return "file://" + filename_; + } + protected: + std::string filename_; int file_; EtiFileType type_; unsigned long nbFrames_; @@ -126,6 +133,11 @@ class DabOutputFifo : public DabOutputFile ~DabOutputFifo() {} int Write(void* buffer, int size); + + std::string get_info() { + return "fifo://" + filename_; + } + }; // -------------- RAW socket ----------- @@ -162,7 +174,12 @@ class DabOutputRaw : public DabOutput int Open(const char* name); int Write(void* buffer, int size); int Close(); + + std::string get_info() { + return "raw://" + filename_; + } private: + std::string filename_; #ifdef _WIN32 HANDLE socket_; #else @@ -197,7 +214,11 @@ class DabOutputUdp : public DabOutput int Write(void* buffer, int size); int Close() { return 0; } + std::string get_info() { + return "udp://" + uri_; + } private: + std::string uri_; UdpSocket* socket_; UdpPacket* packet_; }; @@ -230,9 +251,14 @@ class DabOutputTcp : public DabOutput int Write(void* buffer, int size); int Close(); + std::string get_info() { + return "tcp://" + uri_; + } + TcpServer* server; TcpSocket* client; private: + std::string uri_; pthread_t thread_; }; @@ -252,7 +278,12 @@ class DabOutputSimul : public DabOutput int Open(const char* name); int Write(void* buffer, int size); int Close() { return 0; } + + std::string get_info() { + return "simul://" + name_; + } private: + std::string name_; #ifdef _WIN32 DWORD startTime_; #else @@ -304,6 +335,7 @@ class DabOutputZMQ : public DabOutput { public: DabOutputZMQ() : + endpoint_(""), zmq_proto_(""), zmq_context_(1), zmq_pub_sock_(zmq_context_, ZMQ_PUB), zmq_message_ix(0) @@ -312,6 +344,7 @@ class DabOutputZMQ : public DabOutput } DabOutputZMQ(std::string zmq_proto) : + endpoint_(""), zmq_proto_(zmq_proto), zmq_context_(1), zmq_pub_sock_(zmq_context_, ZMQ_PUB), zmq_message_ix(0) @@ -323,7 +356,11 @@ class DabOutputZMQ : public DabOutput zmq_pub_sock_.close(); } - int Open(const char* name); + std::string get_info() { + return "zmq: " + zmq_proto_ + "://" + endpoint_; + } + + int Open(const char* endpoint); int Write(void* buffer, int size); int Close(); private: @@ -334,6 +371,7 @@ class DabOutputZMQ : public DabOutput /* Forbid copy constructor */ } + std::string endpoint_; std::string zmq_proto_; zmq::context_t zmq_context_; // handle for the zmq context zmq::socket_t zmq_pub_sock_; // handle for the zmq publisher socket diff --git a/src/dabOutput/dabOutputFifo.cpp b/src/dabOutput/dabOutputFifo.cpp index de9579e..6b1c016 100644 --- a/src/dabOutput/dabOutputFifo.cpp +++ b/src/dabOutput/dabOutputFifo.cpp @@ -59,6 +59,7 @@ int DabOutputFifo::Write(void* buffer, int size) if (write(this->file_, padding, 6144 - size) == -1) goto FIFO_WRITE_ERROR; break; + case ETI_FILE_TYPE_NONE: default: etiLog.log(error, "File type is not supported.\n"); return -1; diff --git a/src/dabOutput/dabOutputFile.cpp b/src/dabOutput/dabOutputFile.cpp index f26ddfd..d7fd5c7 100644 --- a/src/dabOutput/dabOutputFile.cpp +++ b/src/dabOutput/dabOutputFile.cpp @@ -112,6 +112,7 @@ int DabOutputFile::Write(void* buffer, int size) memset(padding, 0x55, 6144 - size); if (write(this->file_, padding, 6144 - size) == -1) goto FILE_WRITE_ERROR; break; + case ETI_FILE_TYPE_NONE: default: etiLog.log(error, "File type is not supported.\n"); return -1; diff --git a/src/dabOutput/edi/AFPacket.h b/src/dabOutput/edi/AFPacket.h index 5f62456..9b189b8 100644 --- a/src/dabOutput/edi/AFPacket.h +++ b/src/dabOutput/edi/AFPacket.h @@ -39,6 +39,8 @@ typedef std::vector<uint8_t> AFPacket; class AFPacketiser { public: + AFPacketiser() : + m_verbose(false) {}; AFPacketiser(bool verbose) : m_verbose(verbose) {}; diff --git a/src/dabOutput/edi/PFT.h b/src/dabOutput/edi/PFT.h index 4aae817..9c6f7bd 100644 --- a/src/dabOutput/edi/PFT.h +++ b/src/dabOutput/edi/PFT.h @@ -49,6 +49,14 @@ class PFT public: static const int ParityBytes = 48; + PFT() : + m_k(207), + m_m(3), + m_dest_port(12000), + m_pseq(0), + m_verbose(false) + { } + PFT(unsigned int RSDataWordLength, unsigned int NumRecoverableFragments, const edi_configuration_t &conf) : @@ -25,7 +25,7 @@ #include <errno.h> -const static short bitrateArray[4][4][16] = { +static const short bitrateArray[4][4][16] = { { // MPEG 2.5 { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // layer invalid @@ -69,7 +69,7 @@ const static short bitrateArray[4][4][16] = { }; -const static int samplingrateArray[4][4] = { +static const int samplingrateArray[4][4] = { { 11025, 12000, 8000, 0 }, // MPEG 2.5 { -1, -1, -1, -1 }, // MPEG invalid { 22050, 24000, 16000, 0 }, // MPEG 2 diff --git a/src/utils.cpp b/src/utils.cpp index 7c3c516..42937c1 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -3,7 +3,7 @@ 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2013, 2014 Matthias P. Braendli + Copyright (C) 2013, 2014, 2015 Matthias P. Braendli http://mpb.li */ /* @@ -24,6 +24,7 @@ */ #include <cstring> #include <iostream> +#include <boost/shared_ptr.hpp> #include "DabMux.h" #include "utils.h" @@ -73,7 +74,7 @@ void header_message() fprintf(stderr, "(Communications Research Centre Canada) All rights reserved.\n\n"); fprintf(stderr, - "Copyright (C) 2013, 2014 Matthias P. Braendli\n"); + "Copyright (C) 2013, 2014, 2015 Matthias P. Braendli\n"); fprintf(stderr, "http://opendigitalradio.org\n\n"); @@ -220,12 +221,12 @@ void printUsage(char *name, FILE* out) " within the common interleaved frame.\n" "\n" " __________________________________________________\n" - " | CRC-Ensemble | ENSEMBLE\n" + " | Ensemble | ENSEMBLE\n" " |__________________________________________________|\n" " | | |\n" " | | |\n" " _______V______ _______V______ _______V______\n" - " | CRC-Service1 | | CRC-Service2 | | CRC-Service3 | SERVICES\n" + " | Service 1 | | Service 2 | | Service 3 | SERVICES\n" " |______________| |______________| |______________|\n" " | | | | |______ |\n" " | | | | | |\n" @@ -330,12 +331,12 @@ void printUsage(char *name, FILE* out) " within the common interleaved frame.\n" "\n" " __________________________________________________\n" - " | CRC-Ensemble | ENSEMBLE\n" + " | Ensemble | ENSEMBLE\n" " |__________________________________________________|\n" " | | |\n" " | | |\n" " _______V______ _______V______ _______V______\n" - " | CRC-Service1 | | CRC-Service2 | | CRC-Service3 | SERVICES\n" + " | Service 1 | | Service 2 | | Service 3 | SERVICES\n" " |______________| |______________| |______________|\n" " | | | | |______ |\n" " | | | | | |\n" @@ -352,47 +353,40 @@ void printUsage(char *name, FILE* out) } #endif -void printOutputs(vector<dabOutput*>& outputs) +void printOutputs(vector<boost::shared_ptr<DabOutput> >& outputs) { - vector<dabOutput*>::const_iterator output; int index = 0; - for (output = outputs.begin(); output != outputs.end(); ++output) { + for (auto output : outputs) { etiLog.log(info, "Output %i", index); - etiLog.level(info) << " protocol: " << - (*output)->outputProto; - - etiLog.level(info) << " name: " << - (*output)->outputName.c_str(); - // Daboutputfile mangles with outputName, inserting \0 to - // cut the string in several parts. That doesn't work - // with stl strings. Thats why the .c_str() + etiLog.level(info) << " URI: " << + output->get_info(); ++index; } } -void printServices(vector<DabService*>& services) +void printServices(const vector<shared_ptr<DabService> >& services) { - vector<DabService*>::const_iterator current; int index = 0; - for (current = services.begin(); current != services.end(); ++current) { + for (auto service : services) { - etiLog.level(info) << "Service " << (*current)->get_rc_name(); - etiLog.level(info) << " label: " << (*current)->label.text(); + etiLog.level(info) << "Service " << service->get_rc_name(); + etiLog.level(info) << " label: " << + service->label.long_label(); etiLog.level(info) << " short label: " << - (*current)->label.short_label(); + service->label.short_label(); - etiLog.log(info, " (0x%x)", (*current)->label.flag()); - etiLog.log(info, " id: 0x%lx (%lu)", (*current)->id, - (*current)->id); + etiLog.log(info, " (0x%x)", service->label.flag()); + etiLog.log(info, " id: 0x%lx (%lu)", service->id, + service->id); - etiLog.log(info, " pty: 0x%x (%u)", (*current)->pty, - (*current)->pty); + etiLog.log(info, " pty: 0x%x (%u)", service->pty, + service->pty); etiLog.log(info, " language: 0x%x (%u)", - (*current)->language, (*current)->language); + service->language, service->language); ++index; } } @@ -413,7 +407,8 @@ void printComponent(DabComponent* component) { etiLog.log(info, " service id: %i", component->serviceId); etiLog.log(info, " subchannel id: %i", component->subchId); - etiLog.log(info, " label: %s", component->label.text()); + etiLog.level(info) << " label: " << + component->label.long_label(); etiLog.level(info) << " short label: " << component->label.short_label(); @@ -526,12 +521,13 @@ void printSubchannels(vector<dabSubchannel*>& subchannels) } } -void printEnsemble(dabEnsemble* ensemble) +void printEnsemble(const boost::shared_ptr<dabEnsemble> ensemble) { etiLog.log(info, "Ensemble"); etiLog.log(info, " id: 0x%lx (%lu)", ensemble->id, ensemble->id); etiLog.log(info, " ecc: 0x%x (%u)", ensemble->ecc, ensemble->ecc); - etiLog.log(info, " label: %s", ensemble->label.text()); + etiLog.level(info) << " label: " << + ensemble->label.long_label(); etiLog.level(info) << " short label: " << ensemble->label.short_label(); diff --git a/src/utils.h b/src/utils.h index ee2340f..1756adf 100644 --- a/src/utils.h +++ b/src/utils.h @@ -29,6 +29,7 @@ #define _UTILS_H #include <cstdio> +#include "MuxElements.h" time_t getDabTime(); @@ -49,17 +50,18 @@ void printUsageConfigfile(char *name, FILE* out = stderr); /* The following four utility functions display a * description of all outputs, services, components * resp. subchannels*/ -void printOutputs(std::vector<dabOutput*>& outputs); +void printOutputs(std::vector<boost::shared_ptr<DabOutput> >& outputs); -void printServices(std::vector<DabService*>& services); +void printServices(std::vector<std::shared_ptr<DabService> >& services); void printComponents(std::vector<DabComponent*>& components); void printSubchannels(std::vector<dabSubchannel*>& subchannels); /* Print information about the whole ensemble */ -void printEnsemble(dabEnsemble* ensemble); +void printEnsemble(const boost::shared_ptr<dabEnsemble> ensemble); /* Print detailed component information */ void printComponent(DabComponent* component); #endif + diff --git a/src/zmqinput-keygen.c b/src/zmqinput-keygen.c index 0169837..8bf61ac 100644 --- a/src/zmqinput-keygen.c +++ b/src/zmqinput-keygen.c @@ -58,6 +58,7 @@ int main(int argc, char** argv) int rc = zmq_curve_keypair(public_key, secret_key); if (rc != 0) { fprintf(stderr, "key generation failed\n"); + return 1; } int fdpub = creat(pubkeyfile, S_IRUSR | S_IWUSR); |