diff options
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | TODO | 19 | ||||
-rw-r--r-- | configure.ac | 91 | ||||
-rw-r--r-- | doc/advanced.mux | 32 | ||||
-rw-r--r-- | src/ConfigParser.cpp | 216 | ||||
-rw-r--r-- | src/DabMux.cpp | 18 | ||||
-rw-r--r-- | src/DabMux.h | 7 | ||||
-rw-r--r-- | src/Makefile.am | 22 | ||||
-rw-r--r-- | src/MuxElements.h | 4 | ||||
-rw-r--r-- | src/UdpSocket.cpp | 18 | ||||
-rw-r--r-- | src/UdpSocket.h | 10 | ||||
-rw-r--r-- | src/dabInput.h | 17 | ||||
-rw-r--r-- | src/dabInputMpegFile.cpp | 31 | ||||
-rw-r--r-- | src/input/File.cpp | 443 | ||||
-rw-r--r-- | src/input/File.h | 91 | ||||
-rw-r--r-- | src/input/Prbs.cpp (renamed from src/dabInputPrbs.cpp) | 35 | ||||
-rw-r--r-- | src/input/Prbs.h (renamed from src/dabInputPrbs.h) | 14 | ||||
-rw-r--r-- | src/input/Udp.cpp | 134 | ||||
-rw-r--r-- | src/input/Udp.h | 52 | ||||
-rw-r--r-- | src/input/Zmq.cpp (renamed from src/dabInputZmq.cpp) | 53 | ||||
-rw-r--r-- | src/input/Zmq.h (renamed from src/dabInputZmq.h) | 48 | ||||
-rw-r--r-- | src/input/inputs.h | 52 | ||||
-rw-r--r-- | src/mpeg.c | 26 | ||||
-rw-r--r-- | src/mpeg.h | 7 | ||||
-rw-r--r-- | src/utils.cpp | 20 |
25 files changed, 1157 insertions, 306 deletions
diff --git a/.travis.yml b/.travis.yml index 1153c5b..b21773f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,7 @@ addons: &addons env: - CONF=--disable-output-edi - CONF= - - CONF=--enable-input-prbs --enable-input-test --enable-input-udp --enable-output-raw - - CONF=--disable-format-raw --disable-format-mpeg --disable-format-packet --disable-format-dabplus --disable-format-dmb --disable-format-epm --disable-input-fifo --disable-input-file + - CONF=--enable-output-raw compiler: - gcc @@ -15,6 +15,7 @@ ODR-DabMod to add EDI input there too. Initial work started in http://git.mpb.li/git/odr-edilib/ + Explicit Service Linking ------------------------ At the moment there is no support to signal explicit service linking. @@ -36,20 +37,29 @@ Clarify usage of PTy We currently transmit dynamic PTy in FIG0/17 since it can be changed in through the remote control. Some receivers might not display the dynamic PTy, but only the static PTy. Clarify if we need to add both PTy variants -to the configuration and the code.o +to the configuration and the code. -Refactor inputs ---------------- +Refactor inputs *ONGOING* +------------------------- The input code is written in very C-like OOP, with structures of function pointers that do dynamic dispatch. Refactoring this to proper classes and documenting it properly will simplify the addition of new input formats, facilitate runtime configurability and clarify the usages of the inputs. -Also, all inputs using UDP are now broken. +Also, all inputs using UDP are now broken. Add statistics to UDP input. Find out what purpose the bridge input serves. +Decide if non-blocking file input is still necessary. + + +Fix DMB input +------------- +The code that does interleaving and reed-solomon encoding for DMB is not used +anymore, and is untested. + + Communicate Leap Seconds ------------------------ Actually, we're supposed to say in FIG0/10 when there is a UTC leap second @@ -58,6 +68,7 @@ concept is totally unaware of that, this is not done. We need to know for EDI TIST, and the ClockTAI class can get the information from the Internet, but it is not used in FIG0/10. + Add support for services with different ECC than ensemble --------------------------------------------------------- FIG 0/9 can transmit an Extended field for this information. Needs change of diff --git a/configure.ac b/configure.ac index 47523de..34abe67 100644 --- a/configure.ac +++ b/configure.ac @@ -101,27 +101,6 @@ AC_FUNC_VPRINTF AC_CHECK_FUNCS([bzero gethostbyname gettimeofday inet_ntoa memchr memmove memset socket strchr strdup strerror strrchr strstr strtol strtoul]) # Options -# Inputs -# FIFO -AC_ARG_ENABLE([input_fifo], - [AS_HELP_STRING([--disable-input-fifo], [Disable FIFO input])], - [], [enable_input_fifo=yes]) -AS_IF([test "x$enable_input_fifo" = "xyes"], - [AC_DEFINE(HAVE_INPUT_FIFO, [1], [Define if FIFO input is enabled])]) -# FILE -AC_ARG_ENABLE([input_file], - [AS_HELP_STRING([--disable-input-file], [Disable FILE input])], - [], [enable_input_file=yes]) -AS_IF([test "x$enable_input_file" = "xyes"], - [AC_DEFINE(HAVE_INPUT_FILE, [1], [Define if FILE input is enabled])]) - -# UDP -AC_ARG_ENABLE([input_udp], - [AS_HELP_STRING([--enable-input-udp], [Enable UDP input])], - [], [enable_input_udp=no]) -AS_IF([test "x$enable_input_udp" = "xyes"], - [AC_DEFINE(HAVE_INPUT_UDP, [1], [Define if UDP input is enabled])]) - # Outputs # FILE AC_ARG_ENABLE([output_file], @@ -184,45 +163,6 @@ AM_CONDITIONAL([HAVE_CURL_TEST], AM_CONDITIONAL([HAVE_OUTPUT_RAW_TEST], [test "x$enable_output_raw" = "xyes"]) - -# Formats -# RAW -AC_ARG_ENABLE([format_raw], - [AS_HELP_STRING([--disable-format-raw], [Disable RAW format])], - [], [enable_format_raw=yes]) -AS_IF([test "x$enable_format_raw" = "xyes"], - [AC_DEFINE(HAVE_FORMAT_RAW, [1], [Define if RAW format is enabled])]) -# MPEG -AC_ARG_ENABLE([format_mpeg], - [AS_HELP_STRING([--disable-format-mpeg], [Disable MPEG format])], - [], [enable_format_mpeg=yes]) -AS_IF([test "x$enable_format_mpeg" = "xyes"], - [AC_DEFINE(HAVE_FORMAT_MPEG, [1], [Define if MPEG format is enabled])]) -# PACKET -AC_ARG_ENABLE([format_packet], - [AS_HELP_STRING([--disable-format-packet], [Disable PACKET format])], - [], [enable_format_packet=yes]) -AS_IF([test "x$enable_format_packet" = "xyes"], - [AC_DEFINE(HAVE_FORMAT_PACKET, [1], [Define if PACKET format is enabled])]) -# DABPLUS -AC_ARG_ENABLE([format_dabplus], - [AS_HELP_STRING([--disable-format-dabplus], [Disable DABPLUS format])], - [], [enable_format_dabplus=yes]) -AS_IF([test "x$enable_format_dabplus" = "xyes"], - [AC_DEFINE(HAVE_FORMAT_DABPLUS, [1], [Define if DABPLUS format is enabled])]) -# DMB -AC_ARG_ENABLE([format_dmb], - [AS_HELP_STRING([--disable-format-dmb], [Disable DMB format])], - [], [enable_format_dmb=yes]) -AS_IF([test "x$enable_format_dmb" = "xyes"], - [AC_DEFINE(HAVE_FORMAT_DMB, [1], [Define if DMB format is enabled])]) -# EPM -AC_ARG_ENABLE([format_epm], - [AS_HELP_STRING([--disable-format-epm], [Disable EPM format])], - [], [enable_format_epm=yes]) -AS_IF([test "x$enable_format_epm" = "xyes"], - [AC_DEFINE(HAVE_FORMAT_EPM, [1], [Define if EPM format is enabled])]) - AM_CONDITIONAL([IS_GIT_REPO], [test -d '.git']) AC_CONFIG_FILES([Makefile @@ -235,34 +175,11 @@ AC_OUTPUT echo echo "***********************************************" echo -echo ZeroMQ input, output and management server enabled. +echo "ZeroMQ management server enabled." echo -echo "Inputs:" -enabled="prbs" -disabled="" -for input in udp fifo file -do - eval var=\$enable_input_$input - AS_IF([test "x$var" = "xyes"], - [enabled="$enabled $input"], - [disabled="$disabled $input"]) -done -echo " Enabled: $enabled" -echo " Disabled: $disabled" - +echo "Inputs: prbs udp zmq fifo file" echo -echo "Formats:" -enabled="" -disabled="" -for format in raw mpeg packet dabplus dmb epm -do - eval var=\$enable_format_$format - AS_IF([test "x$var" = "xyes"], - [enabled="$enabled $format"], - [disabled="$disabled $format"]) -done -echo " Enabled: $enabled" -echo " Disabled: $disabled" +echo "Formats: raw mpeg packet dabplus dmb epm" echo echo "Outputs:" @@ -275,7 +192,7 @@ do [enabled="$enabled $output"], [disabled="$disabled $output"]) done -echo " Enabled: $enabled" +echo " Enabled: $enabled zmq" echo " Disabled: $disabled" echo diff --git a/doc/advanced.mux b/doc/advanced.mux index 8d460d2..41a3446 100644 --- a/doc/advanced.mux +++ b/doc/advanced.mux @@ -130,8 +130,17 @@ services { } ; The subchannels are defined in the corresponding section. -; supported types are : audio, bridge, data, enhancedpacket, -; dabplus, dmb, packet, test +; supported types are : audio, data, enhancedpacket, +; dabplus, packet +; +; Type 'packet' expects to receive data in the format described +; in EN 300 401 Clause 3.5.2. +; +; 'enhancedpacket' mode will calculate FEC for MSC packet mode +; as described in EN 300 401 Clause 5.3.5. +; +; 'data' will read from the source and write it unmodified into +; the MSC. subchannels { sub-fu { type audio @@ -235,6 +244,25 @@ subchannels { zmq-buffer 40 zmq-prebuffering 20 } + + ; 'prbs' will generate a pseudorandom bit sequence according to + ; ETS 300 799 Clause G.2.1. This is useful for testing purposes and + ; measurement of bit error rate. + sub-prbs { + type data + + ; Use the default PRBS polynomial. + inputfile "prbs://" + + ; To use another polynomial, set it in the url as hexadecimal + ; The default polynomial is G(x) = x^20 + x^17 + 1, represented as + ; (1 << 20) + (1 << 17) + (1 << 0) = 0x120001 + ;inputuri "prbs://:0x120001 + + bitrate 16 + id 5 + protection 3 + } } ; For now, each component links one service to one subchannel diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 770daa6..1ed1bac 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -48,27 +48,16 @@ #include <map> #include <cstring> #include "dabOutput/dabOutput.h" -#include "dabInput.h" +#include "input/inputs.h" #include "utils.h" -#include "dabInputFile.h" -#include "dabInputFifo.h" -#include "dabInputMpegFile.h" -#include "dabInputMpegFifo.h" -#include "dabInputDabplusFile.h" -#include "dabInputDabplusFifo.h" -#include "dabInputPacketFile.h" -#include "dabInputEnhancedPacketFile.h" -#include "dabInputEnhancedFifo.h" -#include "dabInputUdp.h" -#include "dabInputPrbs.h" -#include "dabInputRawFile.h" -#include "dabInputRawFifo.h" -#include "dabInputDmbFile.h" -#include "dabInputDmbUdp.h" -#include "dabInputZmq.h" #include "DabMux.h" #include "ManagementServer.h" +#include "input/Prbs.h" +#include "input/Zmq.h" +#include "input/File.h" +#include "input/Udp.h" + #ifdef _WIN32 # pragma warning ( disable : 4103 ) @@ -541,13 +530,13 @@ void parse_ptree( } } -static dab_input_zmq_config_t setup_zmq_input( +static Inputs::dab_input_zmq_config_t setup_zmq_input( const boost::property_tree::ptree &pt, const std::string& subchanuid) { using boost::property_tree::ptree_error; - dab_input_zmq_config_t zmqconfig; + Inputs::dab_input_zmq_config_t zmqconfig; try { zmqconfig.buffer_size = pt.get<int>("zmq-buffer"); @@ -621,6 +610,7 @@ static void setup_subchannel_from_ptree(DabSubchannel* subchan, subchan->inputUri = inputUri; +#if OLD_INPUTS // {{{ /* The input is of the old_style type, * with the struct of function pointers, * and needs to be a DabInputCompatible @@ -714,7 +704,7 @@ static void setup_subchannel_from_ptree(DabSubchannel* subchan, } else if (type == "data" and proto == "prbs") { input_is_old_style = false; - subchan->input = new DabInputPrbs(); + subchan->input = make_shared<Inputs::Prbs>(); subchan->type = subchannel_type_t::DataDmb; subchan->bitrate = DEFAULT_DATA_BITRATE; } else if (type == "data") { @@ -928,5 +918,191 @@ static void setup_subchannel_from_ptree(DabSubchannel* subchan, subchan->input = new DabInputCompatible(operations); } // else { it's already been created! } +#endif // 0 }}} + + dabProtection* protection = &subchan->protection; + + const bool nonblock = pt.get("nonblock", false); + if (nonblock) { + etiLog.level(warn) << "The nonblock option is not supported"; + } + + if (type == "dabplus" or type == "audio") { + subchan->type = subchannel_type_t::Audio; + subchan->bitrate = 0; + + if (proto == "file") { + if (type == "audio") { + subchan->input = make_shared<Inputs::MPEGFile>(); + } + else if (type == "dabplus") { + subchan->input = make_shared<Inputs::RawFile>(); + } + else { + throw logic_error("Incomplete handling of file input"); + } + } + else if (proto == "tcp" || + proto == "epgm" || + proto == "ipc") { + + auto zmqconfig = setup_zmq_input(pt, subchanuid); + + if (type == "audio") { + auto inzmq = make_shared<Inputs::ZmqMPEG>(subchanuid, zmqconfig); + rcs.enrol(inzmq.get()); + subchan->input = inzmq; + } + else if (type == "dabplus") { + auto inzmq = make_shared<Inputs::ZmqAAC>(subchanuid, zmqconfig); + rcs.enrol(inzmq.get()); + subchan->input = inzmq; + } + + if (proto == "epgm") { + etiLog.level(warn) << "Using untested epgm:// zeromq input"; + } + else if (proto == "ipc") { + etiLog.level(warn) << "Using untested ipc:// zeromq input"; + } + } + else { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << + ": Invalid protocol for " << type << " input (" << + proto << ")" << endl; + throw runtime_error(ss.str()); + } + } + else if (type == "data" and proto == "prbs") { + subchan->input = make_shared<Inputs::Prbs>(); + subchan->type = subchannel_type_t::DataDmb; + subchan->bitrate = DEFAULT_DATA_BITRATE; + } + else if (type == "data" or type == "dmb") { + if (proto == "udp") { + subchan->input = make_shared<Inputs::Udp>(); + } else if (proto == "file" or proto == "fifo") { + subchan->input = make_shared<Inputs::RawFile>(); + } else { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << + ": Invalid protocol for data input (" << + proto << ")" << endl; + throw runtime_error(ss.str()); + } + + subchan->type = subchannel_type_t::DataDmb; + subchan->bitrate = DEFAULT_DATA_BITRATE; + + if (type == "dmb") { + /* The old dmb input took care of interleaving and Reed-Solomon encoding. This + * code is unported. + * See dabInputDmbFile.cpp + */ + etiLog.level(warn) << "uid " << subchanuid << " of type Dmb uses RAW input"; + } + } + else if (type == "packet" or type == "enhancedpacket") { + subchan->type = subchannel_type_t::Packet; + subchan->bitrate = DEFAULT_PACKET_BITRATE; + + bool enhanced = (type == "enhancedpacket"); + subchan->input = make_shared<Inputs::PacketFile>(enhanced); + } + else { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << " has unknown type!"; + throw runtime_error(ss.str()); + } + subchan->startAddress = 0; + + if (type == "audio") { + protection->form = UEP; + protection->level = 2; + protection->uep.tableIndex = 0; + } + else { + protection->level = 2; + protection->form = EEP; + protection->eep.profile = EEP_A; + } + + /* Get bitrate */ + try { + subchan->bitrate = pt.get<int>("bitrate"); + if ((subchan->bitrate & 0x7) != 0) { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << + ": Bitrate (" << subchan->bitrate << " not a multiple of 8!"; + throw runtime_error(ss.str()); + } + } + catch (ptree_error &e) { + stringstream ss; + ss << "Error, no bitrate defined for subchannel " << subchanuid; + throw runtime_error(ss.str()); + } + + /* Get id */ + try { + subchan->id = hexparse(pt.get<std::string>("id")); + } + catch (ptree_error &e) { + for (int i = 0; i < 64; ++i) { // Find first free subchannel + vector<DabSubchannel*>::iterator subchannel = getSubchannel(ensemble->subchannels, i); + if (subchannel == ensemble->subchannels.end()) { + subchannel = ensemble->subchannels.end() - 1; + subchan->id = i; + break; + } + } + } + + /* Get optional protection profile */ + string profile = pt.get("protection-profile", ""); + + if (profile == "EEP_A") { + protection->form = EEP; + protection->eep.profile = EEP_A; + } + else if (profile == "EEP_B") { + protection->form = EEP; + protection->eep.profile = EEP_B; + } + else if (profile == "UEP") { + protection->form = UEP; + } + + /* Get protection level */ + try { + int level = pt.get<int>("protection"); + + if (protection->form == UEP) { + if ((level < 1) || (level > 5)) { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << + ": protection level must be between " + "1 to 5 inclusively (current = " << level << " )"; + throw runtime_error(ss.str()); + } + } + else if (protection->form == EEP) { + if ((level < 1) || (level > 4)) { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << + ": protection level must be between " + "1 to 4 inclusively (current = " << level << " )"; + throw runtime_error(ss.str()); + } + } + protection->level = level - 1; + } + catch (ptree_error &e) { + stringstream ss; + ss << "Subchannel with uid " << subchanuid << + ": protection level undefined!"; + throw runtime_error(ss.str()); + } } diff --git a/src/DabMux.cpp b/src/DabMux.cpp index 32ddb39..3927420 100644 --- a/src/DabMux.cpp +++ b/src/DabMux.cpp @@ -94,22 +94,8 @@ typedef DWORD32 uint32_t; # include "Eti.h" #endif -#include "dabInputFile.h" -#include "dabInputFifo.h" -#include "dabInputMpegFile.h" -#include "dabInputMpegFifo.h" -#include "dabInputDabplusFile.h" -#include "dabInputDabplusFifo.h" -#include "dabInputPacketFile.h" -#include "dabInputEnhancedPacketFile.h" -#include "dabInputEnhancedFifo.h" -#include "dabInputUdp.h" -#include "dabInputPrbs.h" -#include "dabInputRawFile.h" -#include "dabInputRawFifo.h" -#include "dabInputDmbFile.h" -#include "dabInputDmbUdp.h" - +#include "input/Prbs.h" +#include "input/Zmq.h" #include "dabOutput/dabOutput.h" #include "dabOutput/edi/TagItems.h" diff --git a/src/DabMux.h b/src/DabMux.h index 5dda759..80b4881 100644 --- a/src/DabMux.h +++ b/src/DabMux.h @@ -25,8 +25,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/>. */ -#ifndef _DABMUX_H -#define _DABMUX_H +#pragma once #include <stdint.h> #include <string> @@ -34,7 +33,7 @@ #include "DabMultiplexer.h" #include "RemoteControl.h" #include "dabOutput/dabOutput.h" -#include "dabInput.h" +#include "input/inputs.h" #include "Eti.h" #include "MuxElements.h" @@ -44,5 +43,3 @@ # include <sys/time.h> #endif -#endif - diff --git a/src/Makefile.am b/src/Makefile.am index 408c86e..084cf7b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,23 +47,11 @@ odr_dabmux_LDADD =$(ZMQ_LIBS) $(CURL_LIBS) \ odr_dabmux_SOURCES =DabMux.cpp DabMux.h \ DabMultiplexer.cpp DabMultiplexer.h \ - dabInput.h dabInput.cpp \ - dabInputDabplusFifo.h dabInputDabplusFifo.cpp \ - dabInputDabplusFile.h dabInputDabplusFile.cpp \ - dabInputDmbFile.h dabInputDmbFile.cpp \ - dabInputDmbUdp.h dabInputDmbUdp.cpp \ - dabInputEnhancedFifo.h dabInputEnhancedFifo.cpp \ - dabInputEnhancedPacketFile.h dabInputEnhancedPacketFile.cpp \ - dabInputFifo.h dabInputFifo.cpp \ - dabInputFile.h dabInputFile.cpp \ - dabInputMpegFifo.h dabInputMpegFifo.cpp \ - dabInputMpegFile.h dabInputMpegFile.cpp \ - dabInputPacketFile.h dabInputPacketFile.cpp \ - dabInputPrbs.h dabInputPrbs.cpp \ - dabInputRawFile.h dabInputRawFile.cpp \ - dabInputRawFifo.h dabInputRawFifo.cpp \ - dabInputUdp.h dabInputUdp.cpp \ - dabInputZmq.h dabInputZmq.cpp \ + input/inputs.h \ + input/Prbs.cpp input/Prbs.h \ + input/Zmq.cpp input/Zmq.h \ + input/File.cpp input/File.h \ + input/Udp.cpp input/Udp.h \ dabOutput/dabOutput.h \ dabOutput/dabOutputFile.cpp \ dabOutput/dabOutputFifo.cpp \ diff --git a/src/MuxElements.h b/src/MuxElements.h index 7056121..7324cdc 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -40,7 +40,7 @@ #include <boost/optional.hpp> #include <stdint.h> #include "dabOutput/dabOutput.h" -#include "dabInput.h" +#include "input/inputs.h" #include "RemoteControl.h" #include "Eti.h" @@ -295,7 +295,7 @@ public: std::string uid; std::string inputUri; - DabInputBase* input; + std::shared_ptr<Inputs::InputBase> input; unsigned char id; subchannel_type_t type; uint16_t startAddress; diff --git a/src/UdpSocket.cpp b/src/UdpSocket.cpp index 020e3f5..ccdd7ed 100644 --- a/src/UdpSocket.cpp +++ b/src/UdpSocket.cpp @@ -37,19 +37,19 @@ using namespace std; UdpSocket::UdpSocket() : listenSocket(INVALID_SOCKET) { - init_sock(0, ""); + reinit(0, ""); } UdpSocket::UdpSocket(int port) : listenSocket(INVALID_SOCKET) { - init_sock(port, ""); + reinit(port, ""); } UdpSocket::UdpSocket(int port, const std::string& name) : listenSocket(INVALID_SOCKET) { - init_sock(port, name); + reinit(port, name); } @@ -67,7 +67,7 @@ int UdpSocket::setBlocking(bool block) return 0; } -int UdpSocket::init_sock(int port, const std::string& name) +int UdpSocket::reinit(int port, const std::string& name) { if (listenSocket != INVALID_SOCKET) { ::close(listenSocket); @@ -98,6 +98,16 @@ int UdpSocket::init_sock(int port, const std::string& name) return 0; } +int UdpSocket::close() +{ + if (listenSocket != INVALID_SOCKET) { + ::close(listenSocket); + } + + listenSocket = INVALID_SOCKET; + + return 0; +} UdpSocket::~UdpSocket() { diff --git a/src/UdpSocket.h b/src/UdpSocket.h index 535499e..dfeaac1 100644 --- a/src/UdpSocket.h +++ b/src/UdpSocket.h @@ -80,6 +80,15 @@ class UdpSocket UdpSocket(const UdpSocket& other) = delete; const UdpSocket& operator=(const UdpSocket& other) = delete; + /** reinitialise socket. Close the already open socket, and + * create a new one + */ + int reinit(int port, const std::string& name); + + /** Close the socket + */ + int close(void); + /** Send an UDP packet. * @param packet The UDP packet to be sent. It includes the data and the * destination address @@ -111,7 +120,6 @@ class UdpSocket int setBlocking(bool block); protected: - int init_sock(int port, const std::string& name); /// The address on which the socket is bound. InetAddress address; diff --git a/src/dabInput.h b/src/dabInput.h index d5444cd..d2c5f49 100644 --- a/src/dabInput.h +++ b/src/dabInput.h @@ -29,8 +29,6 @@ #include "RemoteControl.h" #include <string> -extern Logger etiLog; - // TODO replace usage of dabInputOperations by a // class hierarchy struct dabInputOperations { @@ -48,19 +46,6 @@ struct dabInputOperations { bool operator==(const dabInputOperations&); }; -/* New input object base */ -class DabInputBase { - public: - virtual int open(const std::string& name) = 0; - virtual int readFrame(void* buffer, int size) = 0; - virtual int setBitrate(int bitrate) = 0; - virtual int close() = 0; - - virtual ~DabInputBase() {} - protected: - DabInputBase() {} -}; - /* Wrapper class for old-style dabInputOperations inputs */ class DabInputCompatible : public DabInputBase { public: @@ -77,7 +62,7 @@ class DabInputCompatible : public DabInputBase { virtual int setbuf(int size) { return m_ops.setbuf(args, size); } - virtual int readFrame(void* buffer, int size) + virtual int readFrame(uint8_t* buffer, size_t size) { if (m_ops.lock) { m_ops.lock(args); diff --git a/src/dabInputMpegFile.cpp b/src/dabInputMpegFile.cpp index 804ea29..6f24f32 100644 --- a/src/dabInputMpegFile.cpp +++ b/src/dabInputMpegFile.cpp @@ -47,37 +47,6 @@ struct dabInputOperations dabInputMpegFileOperations = { }; -#define MPEG_FREQUENCY -2 -#define MPEG_PADDING -3 -#define MPEG_COPYRIGHT -4 -#define MPEG_ORIGINAL -5 -#define MPEG_EMPHASIS -6 -int checkDabMpegFrame(void* data) { - mpegHeader* header = (mpegHeader*)data; - unsigned long* headerData = (unsigned long*)data; - if ((*headerData & 0x0f0ffcff) == 0x0004fcff) return 0; - if ((*headerData & 0x0f0ffcff) == 0x0004f4ff) return 0; - if (getMpegFrequency(header) != 48000) { - if (getMpegFrequency(header) != 24000) { - return MPEG_FREQUENCY; - } - } - if (header->padding != 0) { - return MPEG_PADDING; - } - if (header->copyright != 0) { - return MPEG_COPYRIGHT; - } - if (header->original != 0) { - return MPEG_ORIGINAL; - } - if (header->emphasis != 0) { - return MPEG_EMPHASIS; - } - return -1; -} - - int dabInputMpegFileRead(dabInputOperations* ops, void* args, void* buffer, int size) { dabInputFileData* data = (dabInputFileData*)args; diff --git a/src/input/File.cpp b/src/input/File.cpp new file mode 100644 index 0000000..5c61fd4 --- /dev/null +++ b/src/input/File.cpp @@ -0,0 +1,443 @@ +/* + Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2016 Matthias P. Braendli + http://www.opendigitalradio.org + + */ +/* + 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 <sstream> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#ifndef _WIN32 +# define O_BINARY 0 +#endif + +#include "input/File.h" +#include "mpeg.h" +#include "ReedSolomon.h" + +namespace Inputs { + +#ifdef _WIN32 +# pragma pack(push, 1) +#endif +struct packetHeader { + unsigned char addressHigh:2; + unsigned char last:1; + unsigned char first:1; + unsigned char continuityIndex:2; + unsigned char packetLength:2; + unsigned char addressLow; + unsigned char dataLength:7; + unsigned char command; +} +#ifdef _WIN32 +# pragma pack(pop) +#else +__attribute((packed)) +#endif +; + + +int FileBase::open(const std::string& name) +{ + m_fd = ::open(name.c_str(), O_RDONLY | O_BINARY); + if (m_fd == -1) { + std::stringstream ss; + ss << "Could not open input file " << name << ": " << + strerror(errno); + throw std::runtime_error(ss.str()); + } + return 0; +} + +int FileBase::setBitrate(int bitrate) +{ + if (bitrate <= 0) { + etiLog.log(error, "Invalid bitrate (%i)\n", bitrate); + return -1; + } + + return bitrate; +} + + +int FileBase::close() +{ + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } + return 0; +} + +int FileBase::rewind() +{ + return ::lseek(m_fd, 0, SEEK_SET); +} + +ssize_t FileBase::readFromFile(uint8_t* buffer, size_t size) +{ + ssize_t ret = read(m_fd, buffer, size); + + if (ret == -1) { + etiLog.log(alert, "ERROR: Can't read file\n"); + perror(""); + return -1; + } + + if (ret < (ssize_t)size) { + ssize_t sizeOut = ret; + etiLog.log(info, "reach end of file -> rewinding\n"); + if (rewind() == -1) { + etiLog.log(alert, "ERROR: Can't rewind file\n"); + return -1; + } + + ret = read(m_fd, buffer + sizeOut, size - sizeOut); + if (ret == -1) { + etiLog.log(alert, "ERROR: Can't read file\n"); + perror(""); + return -1; + } + + if (ret < (ssize_t)size) { + etiLog.log(alert, "ERROR: Not enough data in file\n"); + return -1; + } + } + + return size; +} + +int MPEGFile::readFrame(uint8_t* buffer, size_t size) +{ + int result; + bool do_rewind = false; +READ_SUBCHANNEL: + if (m_parity) { + result = readData(m_fd, buffer, size, 2); + m_parity = false; + return 0; + } else { + result = readMpegHeader(m_fd, buffer, size); + if (result > 0) { + result = readMpegFrame(m_fd, buffer, size); + if (result < 0 && getMpegFrequency(buffer) == 24000) { + m_parity = true; + result = size; + } + } + } + switch (result) { + case MPEG_BUFFER_UNDERFLOW: + etiLog.log(warn, "data underflow -> frame muted\n"); + goto MUTE_SUBCHANNEL; + case MPEG_BUFFER_OVERFLOW: + etiLog.log(warn, "bitrate too high -> frame muted\n"); + goto MUTE_SUBCHANNEL; + case MPEG_FILE_EMPTY: + if (do_rewind) { + etiLog.log(error, "file rewinded and still empty " + "-> frame muted\n"); + goto MUTE_SUBCHANNEL; + } + else { + etiLog.log(info, "reach end of file -> rewinding\n"); + rewind(); + goto READ_SUBCHANNEL; + } + case MPEG_FILE_ERROR: + etiLog.log(alert, "can't read file (%i) -> frame muted\n", errno); + perror(""); + goto MUTE_SUBCHANNEL; + case MPEG_SYNC_NOT_FOUND: + etiLog.log(alert, "mpeg sync not found, maybe is not a valid file " + "-> frame muted\n"); + goto MUTE_SUBCHANNEL; + case MPEG_INVALID_FRAME: + etiLog.log(alert, "file is not a valid mpeg file " + "-> frame muted\n"); + goto MUTE_SUBCHANNEL; + default: + if (result < 0) { + etiLog.log(alert, + "unknown error (code = %i) -> frame muted\n", + result); +MUTE_SUBCHANNEL: + memset(buffer, 0, size); + } + else { + if (result < (ssize_t)size) { + etiLog.log(warn, "bitrate too low from file " + "-> frame padded\n"); + memset((char*)buffer + result, 0, size - result); + } + + result = checkDabMpegFrame(buffer); + switch (result) { + case MPEG_FREQUENCY: + etiLog.log(error, "file has a frame with an invalid " + "frequency: %i, should be 48000 or 24000\n", + getMpegFrequency(buffer)); + break; + case MPEG_PADDING: + etiLog.log(warn, + "file has a frame with padding bit set\n"); + break; + case MPEG_COPYRIGHT: + result = 0; + break; + case MPEG_ORIGINAL: + result = 0; + break; + case MPEG_EMPHASIS: + etiLog.log(warn, + "file has a frame with emphasis bits set\n"); + break; + default: + if (result < 0) { + etiLog.log(alert, "mpeg file has an invalid DAB " + "mpeg frame (unknown reason: %i)\n", result); + } + break; + } + } + } + return result; +} + +int MPEGFile::setBitrate(int bitrate) +{ + if (bitrate == 0) { + uint8_t buffer[4]; + + if (readFrame(buffer, 4) == 0) { + bitrate = getMpegBitrate(buffer); + } + else { + bitrate = -1; + } + rewind(); + } + return bitrate; +} + +int RawFile::readFrame(uint8_t* buffer, size_t size) +{ + return readFromFile(buffer, size); +} + +PacketFile::PacketFile(bool enhancedPacketMode) +{ + m_enhancedPacketEnabled = enhancedPacketMode; +} + +int PacketFile::readFrame(uint8_t* buffer, size_t size) +{ + size_t written = 0; + int length; + packetHeader* header; + int indexRow; + int indexCol; + + while (written < size) { + if (m_enhancedPacketWaiting > 0) { + *buffer = 192 - m_enhancedPacketWaiting; + *buffer /= 22; + *buffer <<= 2; + *(buffer++) |= 0x03; + *(buffer++) = 0xfe; + indexCol = 188; + indexCol += (192 - m_enhancedPacketWaiting) / 12; + indexRow = 0; + indexRow += (192 - m_enhancedPacketWaiting) % 12; + for (int j = 0; j < 22; ++j) { + if (m_enhancedPacketWaiting == 0) { + *(buffer++) = 0; + } + else { + *(buffer++) = m_enhancedPacketData[indexRow][indexCol]; + if (++indexRow == 12) { + indexRow = 0; + ++indexCol; + } + --m_enhancedPacketWaiting; + } + } + written += 24; + if (m_enhancedPacketWaiting == 0) { + m_enhancedPacketLength = 0; + } + } + else if (m_packetLength != 0) { + header = (packetHeader*)(&m_packetData[0]); + if (written + m_packetLength > (unsigned)size) { + memset(buffer, 0, 22); + buffer[22] = 0x60; + buffer[23] = 0x4b; + length = 24; + } + else if (m_enhancedPacketEnabled) { + if (m_enhancedPacketLength + m_packetLength > (12 * 188)) { + memset(buffer, 0, 22); + buffer[22] = 0x60; + buffer[23] = 0x4b; + length = 24; + } + else { + std::copy(m_packetData.begin(), + m_packetData.begin() + m_packetLength, + buffer); + length = m_packetLength; + m_packetLength = 0; + } + } + else { + std::copy(m_packetData.begin(), + m_packetData.begin() + m_packetLength, + buffer); + length = m_packetLength; + m_packetLength = 0; + } + + if (m_enhancedPacketEnabled) { + indexCol = m_enhancedPacketLength / 12; + indexRow = m_enhancedPacketLength % 12; // TODO Check if always 0 + for (int j = 0; j < length; ++j) { + m_enhancedPacketData[indexRow][indexCol] = buffer[j]; + if (++indexRow == 12) { + indexRow = 0; + ++indexCol; + } + } + m_enhancedPacketLength += length; + if (m_enhancedPacketLength >= (12 * 188)) { + m_enhancedPacketLength = (12 * 188); + ReedSolomon encoder(204, 188); + for (int j = 0; j < 12; ++j) { + encoder.encode(&m_enhancedPacketData[j][0], 188); + } + m_enhancedPacketWaiting = 192; + } + } + written += length; + buffer += length; + } + else { + int nbBytes = readFromFile(buffer, 3); + header = (packetHeader*)buffer; + if (nbBytes == -1) { + if (errno == EAGAIN) goto END_PACKET; + perror("Packet file"); + return -1; + } + else if (nbBytes == 0) { + if (rewind() == -1) { + goto END_PACKET; + } + continue; + } + else if (nbBytes < 3) { + etiLog.log(error, + "Error while reading file for packet header; " + "read %i out of 3 bytes\n", nbBytes); + break; + } + + length = header->packetLength * 24 + 24; + if (written + length > size) { + memcpy(&m_packetData[0], header, 3); + readFromFile(&m_packetData[3], length - 3); + m_packetLength = length; + continue; + } + + if (m_enhancedPacketEnabled) { + if (m_enhancedPacketLength + length > (12 * 188)) { + memcpy(&m_packetData[0], header, 3); + readFromFile(&m_packetData[3], length - 3); + m_packetLength = length; + continue; + } + } + + nbBytes = readFromFile(buffer + 3, length - 3); + if (nbBytes == -1) { + perror("Packet file"); + return -1; + } + else if (nbBytes == 0) { + etiLog.log(info, + "Packet header read, but no data!\n"); + if (rewind() == -1) { + goto END_PACKET; + } + continue; + } + else if (nbBytes < length - 3) { + etiLog.log(error, "Error while reading packet file; " + "read %i out of %i bytes\n", nbBytes, length - 3); + break; + } + + if (m_enhancedPacketEnabled) { + indexCol = m_enhancedPacketLength / 12; + indexRow = m_enhancedPacketLength % 12; // TODO Check if always 0 + for (int j = 0; j < length; ++j) { + m_enhancedPacketData[indexRow][indexCol] = buffer[j]; + if (++indexRow == 12) { + indexRow = 0; + ++indexCol; + } + } + m_enhancedPacketLength += length; + if (m_enhancedPacketLength >= (12 * 188)) { + if (m_enhancedPacketLength > (12 * 188)) { + etiLog.log(error, + "Error, too much enhanced packet data!\n"); + } + ReedSolomon encoder(204, 188); + for (int j = 0; j < 12; ++j) { + encoder.encode(&m_enhancedPacketData[j][0], 188); + } + m_enhancedPacketWaiting = 192; + } + } + written += length; + buffer += length; + } + } +END_PACKET: + while (written < size) { + memset(buffer, 0, 22); + buffer[22] = 0x60; + buffer[23] = 0x4b; + buffer += 24; + written += 24; + } + return written; +} + +}; diff --git a/src/input/File.h b/src/input/File.h new file mode 100644 index 0000000..080d6b5 --- /dev/null +++ b/src/input/File.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2016 Matthias P. Braendli + http://www.opendigitalradio.org + + */ +/* + 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/>. + */ + +#pragma once + +#include <vector> +#include <array> +#include <string> +#include <stdint.h> +#include "input/inputs.h" +#include "ManagementServer.h" + +namespace Inputs { + +class FileBase : public InputBase { + public: + virtual int open(const std::string& name); + virtual int readFrame(uint8_t* buffer, size_t size) = 0; + virtual int setBitrate(int bitrate); + virtual int close(); + + /* Rewind the file + * Returns -1 on failure, 0 on success + */ + virtual int rewind(); + protected: + /* Read len bytes from the file into buf, and return + * the number of bytes read, or -1 in case of error. + */ + virtual ssize_t readFromFile(uint8_t* buf, size_t len); + + // We use unix open() instead of fopen() because + // we might want to do non-blocking I/O in the future + int m_fd = -1; +}; + +class MPEGFile : public FileBase { + public: + virtual int readFrame(uint8_t* buffer, size_t size); + virtual int setBitrate(int bitrate); + + private: + bool m_parity = false; +}; + +class RawFile : public FileBase { + public: + virtual int readFrame(uint8_t* buffer, size_t size); +}; + +class PacketFile : public FileBase { + public: + PacketFile(bool enhancedPacketMode); + virtual int readFrame(uint8_t* buffer, size_t size); + + protected: + std::array<uint8_t,96> m_packetData; + size_t m_packetLength; + + /* Enhanced packet mode enables FEC for MSC packet mode + * as described in EN 300 401 Clause 5.3.5 + */ + bool m_enhancedPacketEnabled = false; + std::array<std::array<uint8_t, 204>,12> m_enhancedPacketData; + size_t m_enhancedPacketWaiting; + size_t m_enhancedPacketLength; +}; + +}; diff --git a/src/dabInputPrbs.cpp b/src/input/Prbs.cpp index 2678668..7856a46 100644 --- a/src/dabInputPrbs.cpp +++ b/src/input/Prbs.cpp @@ -26,7 +26,7 @@ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. */ -#include "dabInputPrbs.h" +#include "input/Prbs.h" #include <stdexcept> #include <sstream> @@ -38,23 +38,31 @@ using namespace std; +namespace Inputs { + // ETS 300 799 Clause G.2.1 // Preferred polynomial is G(x) = x^20 + x^17 + 1 -const uint32_t PRBS_DEFAULT_POLY = (1 << 19) | (1 << 16) | 1; +const uint32_t PRBS_DEFAULT_POLY = (1 << 20) | (1 << 17) | (1 << 0); -int DabInputPrbs::open(const string& name) +int Prbs::open(const string& name) { - if (name.empty()) { + if (name.substr(0, 7) != "prbs://") { + throw logic_error("Invalid PRBS name"); + } + + const string& url_polynomial = name.substr(7); + + if (url_polynomial.empty()) { m_prbs.setup(PRBS_DEFAULT_POLY); } else { - if (name[0] != ':') { + if (url_polynomial[0] != ':') { throw invalid_argument( "Invalid PRBS address format. " "Must be prbs://:polynomial."); } - const string poly_str = name.substr(1); + const string poly_str = url_polynomial.substr(1); long polynomial = hexparse(poly_str); @@ -69,30 +77,29 @@ int DabInputPrbs::open(const string& name) return 0; } -int DabInputPrbs::readFrame(void* buffer, int size) +int Prbs::readFrame(uint8_t* buffer, size_t size) { - unsigned char* cbuffer = reinterpret_cast<unsigned char*>(buffer); - - for (int i = 0; i < size; ++i) { - cbuffer[i] = m_prbs.step(); + for (size_t i = 0; i < size; ++i) { + buffer[i] = m_prbs.step(); } return size; } -int DabInputPrbs::setBitrate(int bitrate) +int Prbs::setBitrate(int bitrate) { return bitrate; } -int DabInputPrbs::close() +int Prbs::close() { return 0; } -int DabInputPrbs::rewind() +int Prbs::rewind() { m_prbs.rewind(); return 0; } +}; diff --git a/src/dabInputPrbs.h b/src/input/Prbs.h index 95c5e25..3b2b7d4 100644 --- a/src/dabInputPrbs.h +++ b/src/input/Prbs.h @@ -28,19 +28,17 @@ #pragma once -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - #include <string> -#include "dabInput.h" +#include "input/inputs.h" #include "prbs.h" -class DabInputPrbs : public DabInputBase { +namespace Inputs { + +class Prbs : public InputBase { public: virtual int open(const std::string& name); - virtual int readFrame(void* buffer, int size); + virtual int readFrame(uint8_t* buffer, size_t size); virtual int setBitrate(int bitrate); virtual int close(); @@ -50,3 +48,5 @@ class DabInputPrbs : public DabInputBase { PrbsGenerator m_prbs; }; +}; + diff --git a/src/input/Udp.cpp b/src/input/Udp.cpp new file mode 100644 index 0000000..a238d9b --- /dev/null +++ b/src/input/Udp.cpp @@ -0,0 +1,134 @@ +/* + Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2016 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + 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 "input/Udp.h" + +#include <stdexcept> +#include <sstream> +#include <string.h> +#include <limits.h> +#include <stdlib.h> +#include <errno.h> +#include "utils.h" + +using namespace std; + +namespace Inputs { + +int Udp::open(const std::string& name) +{ + // Skip the udp:// part if it is present + const string endpoint = (name.substr(0, 6) == "udp://") ? + name.substr(6) : name; + + // The endpoint should be address:port + const auto colon_pos = endpoint.find_first_of(":"); + if (colon_pos == string::npos) { + stringstream ss; + ss << "'" << name << + " is an invalid format for udp address: " + "expected [udp://]address:port"; + throw invalid_argument(ss.str()); + } + + const auto address = endpoint.substr(0, colon_pos); + const auto port_str = endpoint.substr(colon_pos + 1); + + const long port = strtol(port_str.c_str(), nullptr, 10); + + if ((port == LONG_MIN or port == LONG_MAX) and errno == ERANGE) { + throw out_of_range("udp input: port out of range"); + } + else if (port == 0 and errno != 0) { + stringstream ss; + ss << "udp input port parse error: " << strerror(errno); + throw invalid_argument(ss.str()); + } + + if (port == 0) { + throw out_of_range("can't use port number 0 in udp address"); + } + + if (m_sock.reinit(port, address) == -1) { + stringstream ss; + ss << "Could not init UDP socket: " << inetErrMsg; + throw runtime_error(ss.str()); + } + + if (m_sock.setBlocking(false) == -1) { + stringstream ss; + ss << "Could not set non-blocking UDP socket: " << inetErrMsg; + throw runtime_error(ss.str()); + } + + return 0; +} + +int Udp::readFrame(uint8_t* buffer, size_t size) +{ + uint8_t* data = reinterpret_cast<uint8_t*>(buffer); + + // Regardless of buffer contents, try receiving data. + UdpPacket packet; + int ret = m_sock.receive(packet); + + if (ret == -1) { + stringstream ss; + ss << "Could not read from UDP socket: " << inetErrMsg; + throw runtime_error(ss.str()); + } + + std::copy(packet.getData(), packet.getData() + packet.getSize(), + back_inserter(m_buffer)); + + // Take data from the buffer if it contains enough data, + // in any case write the buffer + if (m_buffer.size() >= (size_t)size) { + std::copy(m_buffer.begin(), m_buffer.begin() + size, data); + } + else { + memset(data, 0x0, size); + } + + return size; +} + +int Udp::setBitrate(int bitrate) +{ + if (bitrate <= 0) { + etiLog.log(error, "Invalid bitrate (%i)\n", bitrate); + return -1; + } + + return bitrate; +} + +int Udp::close() +{ + return m_sock.close(); +} + +}; diff --git a/src/input/Udp.h b/src/input/Udp.h new file mode 100644 index 0000000..379dbf3 --- /dev/null +++ b/src/input/Udp.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2016 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + 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/>. + */ + +#pragma once + +#include <string> +#include <vector> +#include "input/inputs.h" +#include "UdpSocket.h" + +namespace Inputs { + +class Udp : public InputBase { + public: + virtual int open(const std::string& name); + virtual int readFrame(uint8_t* buffer, size_t size); + virtual int setBitrate(int bitrate); + virtual int close(); + + private: + UdpSocket m_sock; + + // The content of the UDP packets gets written into the + // buffer, and the UDP packet boundaries disappear there. + std::vector<uint8_t> m_buffer; +}; + +}; + diff --git a/src/dabInputZmq.cpp b/src/input/Zmq.cpp index 93f1ea3..a5601fa 100644 --- a/src/dabInputZmq.cpp +++ b/src/input/Zmq.cpp @@ -2,7 +2,7 @@ Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2013, 2014 Matthias P. Braendli + Copyright (C) 2016 Matthias P. Braendli http://www.opendigitalradio.org ZeroMQ input. see www.zeromq.org for more info @@ -39,17 +39,8 @@ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. */ -#include "dabInput.h" -#include "dabInputZmq.h" -#include "PcDebug.h" - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif +#include "input/Zmq.h" -#ifdef HAVE_INPUT_ZEROMQ - -#include "zmq.hpp" #include <cstdio> #include <cstdlib> #include <list> @@ -58,22 +49,28 @@ #include <string> #include <sstream> #include <limits.h> +#include "PcDebug.h" +#include "Log.h" #ifdef __MINGW32__ # define bzero(s, n) memset(s, 0, n) #endif +namespace Inputs { + using namespace std; int readkey(string& keyfile, char* key) { - int fd = open(keyfile.c_str(), O_RDONLY); - if (fd < 0) - return fd; - int ret = read(fd, key, CURVE_KEYLEN); - close(fd); - if (ret < 0) { - return ret; + FILE* fd = fopen(keyfile.c_str(), "r"); + if (fd == nullptr) { + return -1; + } + + int ret = fread(key, CURVE_KEYLEN, 1, fd); + fclose(fd); + if (ret == 0) { + return -1; } /* It needs to be zero-terminated */ @@ -89,7 +86,7 @@ int readkey(string& keyfile, char* key) * keys to the socket, and finally bind the socket * to the new address */ -void DabInputZmqBase::rebind() +void ZmqBase::rebind() { if (! m_zmq_sock_bound_to.empty()) { try { @@ -223,7 +220,7 @@ void DabInputZmqBase::rebind() } } -int DabInputZmqBase::open(const std::string& inputUri) +int ZmqBase::open(const std::string& inputUri) { m_inputUri = inputUri; @@ -236,20 +233,20 @@ int DabInputZmqBase::open(const std::string& inputUri) return 0; } -int DabInputZmqBase::close() +int ZmqBase::close() { m_zmq_sock.close(); return 0; } -int DabInputZmqBase::setBitrate(int bitrate) +int ZmqBase::setBitrate(int bitrate) { m_bitrate = bitrate; return bitrate; // TODO do a nice check here } // size corresponds to a frame size. It is constant for a given bitrate -int DabInputZmqBase::readFrame(void* buffer, int size) +int ZmqBase::readFrame(uint8_t* buffer, size_t size) { int rc; @@ -340,7 +337,7 @@ int DabInputZmqBase::readFrame(void* buffer, int size) /******** MPEG input *******/ // Read a MPEG frame from the socket, and push to list -int DabInputZmqMPEG::readFromSocket(size_t framesize) +int ZmqMPEG::readFromSocket(size_t framesize) { bool messageReceived = false; zmq::message_t msg; @@ -410,7 +407,7 @@ int DabInputZmqMPEG::readFromSocket(size_t framesize) // Read a AAC+ superframe from the socket, cut it into five frames, // and push to list -int DabInputZmqAAC::readFromSocket(size_t framesize) +int ZmqAAC::readFromSocket(size_t framesize) { bool messageReceived; zmq::message_t msg; @@ -496,7 +493,7 @@ int DabInputZmqAAC::readFromSocket(size_t framesize) /********* REMOTE CONTROL ***********/ -void DabInputZmqBase::set_parameter(const string& parameter, +void ZmqBase::set_parameter(const string& parameter, const string& value) { if (parameter == "buffer") { @@ -576,7 +573,7 @@ void DabInputZmqBase::set_parameter(const string& parameter, } } -const string DabInputZmqBase::get_parameter(const string& parameter) const +const string ZmqBase::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "buffer") { @@ -615,5 +612,5 @@ const string DabInputZmqBase::get_parameter(const string& parameter) const } -#endif +}; diff --git a/src/dabInputZmq.h b/src/input/Zmq.h index 351fb07..8d729e0 100644 --- a/src/dabInputZmq.h +++ b/src/input/Zmq.h @@ -2,7 +2,7 @@ Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2013, 2014 Matthias P. Braendli + Copyright (C) 2016 Matthias P. Braendli http://www.opendigitalradio.org ZeroMQ input. see www.zeromq.org for more info @@ -41,48 +41,37 @@ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DAB_INPUT_ZMQ_H -#define DAB_INPUT_ZMQ_H - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#ifdef HAVE_INPUT_ZEROMQ +#pragma once #include <list> #include <string> #include <stdint.h> #include "zmq.hpp" -#include "dabInput.h" +#include "input/inputs.h" #include "ManagementServer.h" +namespace Inputs { + /* The frame_buffer contains DAB logical frames as defined in * TS 102 563, section 6. * Five elements of this buffer make one AAC superframe (120ms audio) */ -// Number of elements to prebuffer before starting the pipeline -#define INPUT_ZMQ_DEF_PREBUFFERING (5*4) // 480ms - -// Default frame_buffer size in number of elements -#define INPUT_ZMQ_DEF_BUFFER_SIZE (5*8) // 960ms - // Minimum frame_buffer size in number of elements // This is one AAC superframe, but you probably don't want to // go that low anyway. -#define INPUT_ZMQ_MIN_BUFFER_SIZE (5*1) // 120ms +const size_t INPUT_ZMQ_MIN_BUFFER_SIZE = 5*1; // 120ms // Maximum frame_buffer size in number of elements // One minute is clearly way over what everybody would // want. -#define INPUT_ZMQ_MAX_BUFFER_SIZE (5*500) // 60s +const size_t INPUT_ZMQ_MAX_BUFFER_SIZE = 5*500; // 60s /* The ZeroMQ Curve key is 40 bytes long in Z85 representation * * But we need to store it as zero-terminated string. */ -#define CURVE_KEYLEN 40 +const size_t CURVE_KEYLEN = 40; /* helper to invalidate a key */ #define INVALIDATE_KEY(k) memset(k, 0, CURVE_KEYLEN+1) @@ -156,9 +145,9 @@ struct zmq_frame_header_t #define ZMQ_FRAME_DATA(f) ( ((uint8_t*)f)+sizeof(zmq_frame_header_t) ) -class DabInputZmqBase : public DabInputBase, public RemoteControllable { +class ZmqBase : public InputBase, public RemoteControllable { public: - DabInputZmqBase(const std::string name, + ZmqBase(const std::string name, dab_input_zmq_config_t config) : RemoteControllable(name), m_zmq_context(1), @@ -192,7 +181,7 @@ class DabInputZmqBase : public DabInputBase, public RemoteControllable { } virtual int open(const std::string& inputUri); - virtual int readFrame(void* buffer, int size); + virtual int readFrame(uint8_t* buffer, size_t size); virtual int setBitrate(int bitrate); virtual int close(); @@ -238,11 +227,11 @@ class DabInputZmqBase : public DabInputBase, public RemoteControllable { size_t m_prebuf_current; }; -class DabInputZmqMPEG : public DabInputZmqBase { +class ZmqMPEG : public ZmqBase { public: - DabInputZmqMPEG(const std::string name, + ZmqMPEG(const std::string name, dab_input_zmq_config_t config) - : DabInputZmqBase(name, config) { + : ZmqBase(name, config) { RC_ADD_PARAMETER(buffer, "Size of the input buffer [mpeg frames]"); @@ -254,11 +243,11 @@ class DabInputZmqMPEG : public DabInputZmqBase { virtual int readFromSocket(size_t framesize); }; -class DabInputZmqAAC : public DabInputZmqBase { +class ZmqAAC : public ZmqBase { public: - DabInputZmqAAC(const std::string name, + ZmqAAC(const std::string name, dab_input_zmq_config_t config) - : DabInputZmqBase(name, config) { + : ZmqBase(name, config) { RC_ADD_PARAMETER(buffer, "Size of the input buffer [aac superframes]"); @@ -270,7 +259,6 @@ class DabInputZmqAAC : public DabInputZmqBase { virtual int readFromSocket(size_t framesize); }; -#endif // HAVE_INPUT_ZMQ +}; -#endif // DAB_INPUT_ZMQ_H diff --git a/src/input/inputs.h b/src/input/inputs.h new file mode 100644 index 0000000..bfb1fb6 --- /dev/null +++ b/src/input/inputs.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in + Right of Canada (Communications Research Center Canada) + + Copyright (C) 2016 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + 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/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "Log.h" +#include "RemoteControl.h" +#include <string> + +namespace Inputs { + +/* New input object base */ +class InputBase { + public: + virtual int open(const std::string& name) = 0; + virtual int readFrame(uint8_t* buffer, size_t size) = 0; + virtual int setBitrate(int bitrate) = 0; + virtual int close() = 0; + + virtual ~InputBase() {} + protected: + InputBase() {} +}; + +}; + @@ -219,3 +219,29 @@ int readMpegFrame(int file, void* data, int size) } return framelength; } + +int checkDabMpegFrame(void* data) { + mpegHeader* header = (mpegHeader*)data; + unsigned long* headerData = (unsigned long*)data; + if ((*headerData & 0x0f0ffcff) == 0x0004fcff) return 0; + if ((*headerData & 0x0f0ffcff) == 0x0004f4ff) return 0; + if (getMpegFrequency(header) != 48000) { + if (getMpegFrequency(header) != 24000) { + return MPEG_FREQUENCY; + } + } + if (header->padding != 0) { + return MPEG_PADDING; + } + if (header->emphasis != 0) { + return MPEG_EMPHASIS; + } + if (header->copyright != 0) { + return MPEG_COPYRIGHT; + } + if (header->original != 0) { + return MPEG_ORIGINAL; + } + return -1; +} + @@ -75,6 +75,13 @@ ssize_t readData(int file, void* data, size_t size, unsigned int tries); int readMpegHeader(int file, void* data, int size); int readMpegFrame(int file, void* data, int size); +#define MPEG_FREQUENCY -2 +#define MPEG_PADDING -3 +#define MPEG_COPYRIGHT -4 +#define MPEG_ORIGINAL -5 +#define MPEG_EMPHASIS -6 +int checkDabMpegFrame(void* data); + #ifdef __cplusplus } #endif diff --git a/src/utils.cpp b/src/utils.cpp index e26389d..cf57170 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -103,36 +103,16 @@ void header_message() std::cerr << "Input URLs supported:" << std::endl << " prbs" << -#if defined(HAVE_INPUT_UDP) " udp" << -#endif -#if defined(HAVE_INPUT_FIFO) - " fifo" << -#endif -#if defined(HAVE_INPUT_FILE) " file" << -#endif -#if defined(HAVE_INPUT_ZEROMQ) " zmq" << -#endif std::endl; std::cerr << "Inputs format supported:" << std::endl << -#if defined(HAVE_FORMAT_RAW) " raw" << -#endif -#if defined(HAVE_FORMAT_MPEG) " mpeg" << -#endif -#if defined(HAVE_FORMAT_PACKET) " packet" << -#endif -#if defined(HAVE_FORMAT_DMB) - " dmb" << -#endif -#if defined(HAVE_FORMAT_EPM) " epm" << -#endif std::endl; std::cerr << "Output URLs supported:" << std::endl << |