diff options
Diffstat (limited to 'src/odr-sourcecompanion.cpp')
-rw-r--r-- | src/odr-sourcecompanion.cpp | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/src/odr-sourcecompanion.cpp b/src/odr-sourcecompanion.cpp new file mode 100644 index 0000000..726a738 --- /dev/null +++ b/src/odr-sourcecompanion.cpp @@ -0,0 +1,458 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2017 Matthias P. Braendli + * Copyright (C) 2017 AVT GmbH - Fabien Vercasson + * Copyright (C) 2011 Martin Storsjo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * ------------------------------------------------------------------- + */ + +/*! \mainpage Introduction + * \file odr-sourcecompanion.cpp + * \brief The main file for the audio encoder + */ + +#include "config.h" +#include "zmq.hpp" + +#include "AVTInput.h" +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +extern "C" { +#include "encryption.h" +#include "utils.h" +} + +#include <vector> +#include <deque> +#include <chrono> +#include <thread> +#include <string> +#include <getopt.h> +#include <cstdio> +#include <stdint.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> + + +using namespace std; + +void usage(const char* name) { + fprintf(stderr, + "ODR-SourceCompanion %s\n" + "\nUsage:\n" + "%s [INPUT SELECTION] [OPTION...]\n", +#if defined(GITVERSION) + GITVERSION +#else + PACKAGE_VERSION +#endif + , name); + fprintf(stderr, + " For the AVT input:\n" +#if HAVE_AVT + " Using the option -I will switch to AVT encoder reception mode:\n" + " * The internal encoder is not used any more, all input related options are ignored\n" + " * The audio mode and bitrate will be sent to the encoder if option --control-uri\n" + " and DAB+ specific options are set (-b -c -r --aaclc --sbr --ps)\n" + " * PAD Data can be send to the encoder with the options --pad-port --pad --pad-fifo\n" + " -I, --input-uri=URI Input URI. (Supported: 'udp://...')\n" + " --control-uri=URI Output control URI (Supported: 'udp://...')\n" + " --timeout=ms Maximum frame waiting time, in milliseconds (def=2000)\n" + " --pad-port=port Port opened for PAD Frame requests (def=0 not opened)\n" + " --jitter-size=nbFrames Jitter buffer size, in 24ms frames (def=40)\n" +#endif + " Encoder parameters:\n" + " -b, --bitrate={ 8, 16, ..., 192 } Output bitrate in kbps. Must be a multiple of 8.\n" + " -c, --channels={ 1, 2 } Nb of input channels (default: 2).\n" + " -r, --rate={ 32000, 48000 } Input sample rate (default: 48000).\n" + " --aaclc Force the usage of AAC-LC (no SBR, no PS)\n" + " --sbr Force the usage of SBR\n" + " --ps Force the usage of PS\n" + " Output and pad parameters:\n" + " -o, --output=URI Output ZMQ uri. (e.g. 'tcp://localhost:9000')\n" + " -or- Output file uri. (e.g. 'file.dabp')\n" + " -or- a single dash '-' to denote stdout\n" + " If more than one ZMQ output is given, the socket\n" + " will be connected to all listed endpoints.\n" + " -k, --secret-key=FILE Enable ZMQ encryption with the given secret key.\n" + " -p, --pad=BYTES Set PAD size in bytes.\n" + " -P, --pad-fifo=FILENAME Set PAD data input fifo name" + " (default: /tmp/pad.fifo).\n" + " -l, --level Show peak audio level indication.\n" + "\n" + "Only the tcp:// zeromq transport has been tested until now,\n" + " but epgm:// and pgm:// are also accepted\n" + ); + +} + + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +int main(int argc, char *argv[]) +{ + std::string avt_input_uri = ""; + std::string avt_output_uri = ""; + int32_t avt_timeout = 2000; + uint32_t avt_pad_port = 0; + size_t avt_jitterBufferSize = 40; + bool avt_mode = false; + + std::vector<std::string> output_uris; + + /* For MOT Slideshow and DLS insertion */ + const char* pad_fifo = "/tmp/pad.fifo"; + int pad_fd; + int padlen = 0; + + /* Whether to show the 'sox'-like measurement */ + int show_level = 0; + + /* Data for ZMQ CURVE authentication */ + char* keyfile = NULL; + char secretkey[CURVE_KEYLEN+1]; + + const struct option longopts[] = { + {"bitrate", required_argument, 0, 'b'}, + {"channels", required_argument, 0, 'c'}, + {"format", required_argument, 0, 'f'}, + {"output", required_argument, 0, 'o'}, + {"pad", required_argument, 0, 'p'}, + {"pad-fifo", required_argument, 0, 'P'}, + {"rate", required_argument, 0, 'r'}, + {"secret-key", required_argument, 0, 'k'}, + {"input-uri", required_argument, 0, 'I'}, + {"control-uri", required_argument, 0, 6 }, + {"timeout", required_argument, 0, 7 }, + {"pad-port", required_argument, 0, 8 }, + {"jitter-size", required_argument, 0, 9 }, + {"aaclc", no_argument, 0, 0 }, + {"help", no_argument, 0, 'h'}, + {"level", no_argument, 0, 'l'}, + {"no-afterburner", no_argument, 0, 'A'}, + {"ps", no_argument, 0, 2 }, + {"sbr", no_argument, 0, 1 }, + {0, 0, 0, 0}, + }; + + fprintf(stderr, + "Welcome to %s %s, compiled at %s, %s", + PACKAGE_NAME, +#if defined(GITVERSION) + GITVERSION, +#else + PACKAGE_VERSION, +#endif + __DATE__, __TIME__); + fprintf(stderr, "\n"); + fprintf(stderr, " http://opendigitalradio.org\n\n"); + + + if (argc < 2) { + usage(argv[0]); + return 1; + } + + bool allowSBR = false; + bool allowPS = false; + + int bitrate = 0; + int channels = 2; + int sample_rate = 48000; + char ch = 0; + int index; + while(ch != -1) { + ch = getopt_long(argc, argv, "aAhDlVb:c:f:i:j:k:L:o:r:d:p:P:s:v:w:I:C:Wg:C:", longopts, &index); + switch (ch) { + case 0: // AAC-LC + allowPS = false; + allowSBR = false; + break; + case 1: // SBR + allowPS = false; + allowSBR = true; + break; + case 2: // PS + allowPS = true; + allowSBR = true; + break; + case 'b': + bitrate = atoi(optarg); + break; + case 'c': + channels = atoi(optarg); + break; + case 'k': + keyfile = optarg; + break; + case 'l': + show_level = 1; + break; + case 'o': + output_uris.push_back(optarg); + break; + case 'p': + padlen = atoi(optarg); + break; + case 'P': + pad_fifo = optarg; + break; + case 'r': + sample_rate = atoi(optarg); + break; + case 'I': + avt_input_uri = optarg; + avt_mode = true; + fprintf(stderr, "AVT Encoder Mode\n"); + break; + case 6: + avt_output_uri = optarg; + break; + case 7: + avt_timeout = atoi(optarg); + if (avt_timeout < 0) { + avt_timeout = 2000; + } + break; + case 8: + avt_pad_port = atoi(optarg); + break; + case 9: + avt_jitterBufferSize = atoi(optarg); + break; + case '?': + case 'h': + usage(argv[0]); + return 1; + } + } + + if (padlen < 0) { + fprintf(stderr, "Invalid PAD length specified\n"); + return 1; + } + + zmq::context_t zmq_ctx; + zmq::socket_t zmq_sock(zmq_ctx, ZMQ_PUB); + + if (not output_uris.empty()) { + for (auto uri : output_uris) { + if (keyfile) { + fprintf(stderr, "Enabling encryption\n"); + + int rc = readkey(keyfile, secretkey); + if (rc) { + fprintf(stderr, "Error reading secret key\n"); + return 2; + } + + const int yes = 1; + zmq_sock.setsockopt(ZMQ_CURVE_SERVER, + &yes, sizeof(yes)); + + zmq_sock.setsockopt(ZMQ_CURVE_SECRETKEY, + secretkey, CURVE_KEYLEN); + } + zmq_sock.connect(uri.c_str()); + } + } + else { + fprintf(stderr, "No output URI defined\n"); + return 1; + } + + if (padlen != 0) { + int flags; + if (mkfifo(pad_fifo, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) != 0) { + if (errno != EEXIST) { + fprintf(stderr, "Can't create pad file: %d!\n", errno); + return 1; + } + } + pad_fd = open(pad_fifo, O_RDONLY | O_NONBLOCK); + if (pad_fd == -1) { + fprintf(stderr, "Can't open pad file!\n"); + return 1; + } + flags = fcntl(pad_fd, F_GETFL, 0); + if (fcntl(pad_fd, F_SETFL, flags | O_NONBLOCK)) { + fprintf(stderr, "Can't set non-blocking mode in pad file!\n"); + return 1; + } + } + + AVTInput avtinput(avt_input_uri, avt_output_uri, avt_pad_port, avt_jitterBufferSize); + + if (avt_input_uri != "") { + if (avtinput.prepare() != 0) { + fprintf(stderr, "Fail to connect to AVT encoder in:'%s' out:'%s'\n", avt_input_uri.c_str(), avt_output_uri.c_str()); + return 1; + } + + // Audio parameters + if (avtinput.setDabPlusParameters(bitrate, channels, sample_rate, allowSBR, allowPS) != 0) { + fprintf(stderr, "Wrong audio parameters for AVT encoder\n"); + return 1; + } + } + else { + fprintf(stderr, "No input defined\n"); + return 1; + } + + int outbuf_size; + std::vector<uint8_t> zmqframebuf; + std::vector<uint8_t> outbuf; + + outbuf_size = bitrate/8*120; + outbuf.resize(24*120); + zmqframebuf.resize(ZMQ_HEADER_SIZE + 24*120); + + if(outbuf_size % 5 != 0) { + fprintf(stderr, "Warning: (outbuf_size mod 5) = %d\n", outbuf_size % 5); + } + + zmq_frame_header_t *zmq_frame_header = (zmq_frame_header_t*)&zmqframebuf[0]; + + unsigned char pad_buf[padlen + 1]; + + fprintf(stderr, "Starting encoding\n"); + + int retval = 0; + int send_error_count = 0; + + int peak_left = 0; + int peak_right = 0; + + int calls = 0; // for checking + ssize_t read_bytes = 0; + size_t numOutBytes = 0; + do { + read_bytes = 0; + + // -------------- Read Data + memset(&outbuf[0], 0x00, outbuf_size); + + const auto timeout_start = std::chrono::steady_clock::now(); + const auto timeout_duration = std::chrono::milliseconds(avt_timeout); + int wait_ms = 1; + + bool timedout = false; + + while ( !timedout && numOutBytes == 0 ) + { + // Fill the PAD Frame queue because multiple PAD frame requests + // can come for each DAB+ Frames (up to 6), + if (padlen != 0) { + int ret = 0; + do { + ret = 0; + if (!avtinput.padQueueFull()) { + + // Non blocking read of the pipe + fd_set read_fd_set; + FD_ZERO(&read_fd_set); + FD_SET(pad_fd, &read_fd_set); + struct timeval to = { 0, 0 }; + if( select(pad_fd+1, &read_fd_set, NULL, NULL, &to) > 0 ) { + ret = read(pad_fd, pad_buf, padlen + 1); + if (ret>0) { + const int calculated_padlen = pad_buf[padlen]; + if (calculated_padlen > 0) { + avtinput.pushPADFrame(pad_buf + (padlen - calculated_padlen), calculated_padlen); + } + } + } + } + } while (ret!=0); + } + + numOutBytes = avtinput.getNextFrame(outbuf); + if (numOutBytes == 0) { + const auto curTime = std::chrono::steady_clock::now(); + const auto diff = curTime - timeout_start; + if (diff > timeout_duration) { + fprintf(stderr, "timeout reached\n"); + timedout = true; + } else { + usleep(wait_ms * 1000); + } + } + } + read_bytes = numOutBytes; + + if (numOutBytes != 0) { + // ------------ ZeroMQ transmit + try { + zmq_frame_header->encoder = ZMQ_ENCODER_FDK; + zmq_frame_header->version = 1; + zmq_frame_header->datasize = numOutBytes; + zmq_frame_header->audiolevel_left = peak_left; + zmq_frame_header->audiolevel_right = peak_right; + + assert(ZMQ_FRAME_SIZE(zmq_frame_header) <= zmqframebuf.size()); + + memcpy(ZMQ_FRAME_DATA(zmq_frame_header), + &outbuf[0], numOutBytes); + zmq_sock.send(&zmqframebuf[0], ZMQ_FRAME_SIZE(zmq_frame_header), + ZMQ_DONTWAIT); + } + catch (zmq::error_t& e) { + fprintf(stderr, "ZeroMQ send error !\n"); + send_error_count ++; + } + + if (send_error_count > 10) + { + fprintf(stderr, "ZeroMQ send failed ten times, aborting!\n"); + retval = 4; + break; + } + } + + if (numOutBytes != 0) + { + if (show_level) { + if (channels == 1) { + fprintf(stderr, "\rIn: [%-6s]", + level(1, MAX(peak_right, peak_left))); + } + else if (channels == 2) { + fprintf(stderr, "\rIn: [%6s|%-6s]", + level(0, peak_left), + level(1, peak_right)); + } + } + + peak_right = 0; + peak_left = 0; + } + } while (read_bytes > 0); + + fprintf(stderr, "\n"); + + zmq_sock.close(); + + return retval; +} + |