/*
Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications
Research Center Canada)
Copyright (C) 2019
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 .
*/
#include "input/Edi.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "utils.h"
using namespace std;
namespace Inputs {
constexpr bool VERBOSE = false;
constexpr size_t TCP_BLOCKSIZE = 2048;
constexpr size_t MAX_FRAMES_QUEUED = 1000;
Edi::Edi() :
m_tcp_receive_server(TCP_BLOCKSIZE),
m_sti_writer(),
m_sti_decoder(m_sti_writer, VERBOSE)
{ }
Edi::~Edi() {
m_running = false;
if (m_thread.joinable()) {
m_thread.join();
}
}
void Edi::open(const std::string& name)
{
const std::regex re_udp("udp://:([0-9]+)");
const std::regex re_tcp("tcp://(.*):([0-9]+)");
lock_guard lock(m_mutex);
m_running = false;
if (m_thread.joinable()) {
m_thread.join();
}
std::smatch m;
if (std::regex_match(name, m, re_udp)) {
const int udp_port = std::stoi(m[1].str());
m_input_used = InputUsed::UDP;
m_udp_sock.reinit(udp_port);
m_udp_sock.setBlocking(false);
// TODO multicast
}
else if (std::regex_match(name, m, re_tcp)) {
m_input_used = InputUsed::TCP;
const string addr = m[1].str();
const int tcp_port = std::stoi(m[2].str());
m_tcp_receive_server.start(tcp_port, addr);
}
else {
throw runtime_error("Cannot parse EDI input URI");
}
m_name = name;
m_running = true;
m_thread = std::thread(&Edi::m_run, this);
}
int Edi::readFrame(uint8_t* buffer, size_t size)
{
if (m_pending_sti_frame.frame.empty()) {
m_frames.try_pop(m_pending_sti_frame);
}
if (not m_pending_sti_frame.frame.empty()) {
if (m_pending_sti_frame.frame.size() != size) {
etiLog.level(debug) << "EDI input " << m_name << " size mismatch: " <<
m_pending_sti_frame.frame.size() << " received, " << size << " requested";
memset(buffer, 0, size * sizeof(*buffer));
}
else {
const auto now = chrono::system_clock::now();
if (m_pending_sti_frame.timestamp.to_system_clock() <= now) {
etiLog.level(debug) << "EDI input take frame with TS " <<
m_pending_sti_frame.timestamp.to_string() << " queue size " << m_frames.size();
std::copy(m_pending_sti_frame.frame.cbegin(), m_pending_sti_frame.frame.cend(), buffer);
m_pending_sti_frame.frame.clear();
}
else {
etiLog.level(debug) << "EDI input skip frame with TS " <<
m_pending_sti_frame.timestamp.to_string() << " queue size " << m_frames.size();
}
}
}
return size;
#if 0
EdiDecoder::sti_frame_t sti;
if (m_is_prebuffering) {
m_is_prebuffering = m_frames.size() < 10;
if (not m_is_prebuffering) {
etiLog.level(info) << "EDI input " << m_name << " pre-buffering complete.";
}
}
else if (m_frames.try_pop(sti)) {
if (sti.frame.size() == 0) {
etiLog.level(debug) << "EDI input " << m_name << " empty frame";
return 0;
}
else if (sti.frame.size() == size) {
std::copy(sti.frame.cbegin(), sti.frame.cend(), buffer);
}
else {
etiLog.level(debug) << "EDI input " << m_name << " size mismatch: " << sti.frame.size() <<
" received, " << size << " requested";
memset(buffer, 0, size * sizeof(*buffer));
}
}
else {
memset(buffer, 0, size * sizeof(*buffer));
m_is_prebuffering = true;
etiLog.level(info) << "EDI input " << m_name << " re-enabling pre-buffering";
}
return size;
#endif
}
void Edi::m_run()
{
while (m_running) {
bool work_done = false;
switch (m_input_used) {
case InputUsed::UDP:
{
constexpr size_t packsize = 2048;
const auto packet = m_udp_sock.receive(packsize);
if (packet.buffer.size() == packsize) {
fprintf(stderr, "Warning, possible UDP truncation\n");
}
if (not packet.buffer.empty()) {
m_sti_decoder.push_packet(packet.buffer);
work_done = true;
}
}
break;
case InputUsed::TCP:
{
auto packet = m_tcp_receive_server.receive();
if (not packet.empty()) {
m_sti_decoder.push_bytes(packet);
work_done = true;
}
}
break;
default:
throw logic_error("unimplemented input");
}
const auto sti = m_sti_writer.getFrame();
if (not sti.frame.empty()) {
m_frames.push_wait_if_full(move(sti), MAX_FRAMES_QUEUED);
work_done = true;
}
if (not work_done) {
// Avoid fast loop
this_thread::sleep_for(chrono::milliseconds(12));
}
}
}
int Edi::setBitrate(int bitrate)
{
if (bitrate <= 0) {
etiLog.level(error) << "Invalid bitrate (" << bitrate << ") for " << m_name;
return -1;
}
return bitrate;
}
int Edi::close()
{
m_udp_sock.close();
return 0;
}
}