From 3d3ff08eda1aea4f45ea479ec1376385f65302d5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 5 Nov 2021 06:10:06 -0700 Subject: [PATCH] a start --- .gitignore | 10 +- Controller.cpp | 106 ++++++++++ Controller.h | 47 +++++ DV3003.cpp | 465 +++++++++++++++++++++++++++++++++++++++++++ DV3003.h | 115 +++++++++++ README.md | 4 +- TranscoderPacket.cpp | 115 +++++++++++ TranscoderPacket.h | 65 ++++++ UnixPacketSocket.cpp | 219 ++++++++++++++++++++ UnixPacketSocket.h | 58 ++++++ 10 files changed, 1200 insertions(+), 4 deletions(-) create mode 100644 Controller.cpp create mode 100644 Controller.h create mode 100644 DV3003.cpp create mode 100644 DV3003.h create mode 100644 TranscoderPacket.cpp create mode 100644 TranscoderPacket.h create mode 100644 UnixPacketSocket.cpp create mode 100644 UnixPacketSocket.h diff --git a/.gitignore b/.gitignore index 259148f..cc0eafc 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,11 @@ *.a *.lib +# Visual Studio +.vscode + +#files +configure.h + # Executables -*.exe -*.out -*.app +tcd diff --git a/Controller.cpp b/Controller.cpp new file mode 100644 index 0000000..7bcfdc2 --- /dev/null +++ b/Controller.cpp @@ -0,0 +1,106 @@ +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "Controller.h" + +bool CController::Start() +{ + if (socket.Open("urfd2tcd", this)) + { + keep_running = false; + return true; + } + future = std::async(std::launch::async, &CController::Processing, this); + return false; +} + +void CController::Stop() +{ + keep_running = false; + future.get(); + socket.Close(); +} + +void CController::Processing() +{ + while (keep_running) + { + // anything to read? + } +} + +bool CController::InitDevices() +{ + // unpack all the device paths + std::set deviceset; + CSVtoSet(DEVICES, deviceset); + if (2 > deviceset.size()) + { + std::cerr << "You must specify at least two DVSI 3003 devices" << std::endl; + return true; + } + + // now initialize each device + + // the first one will be a dstar device + Encoding type = Encoding::dstar; + for (const auto devpath : deviceset) + { + // instantiate it + auto a3003 = std::make_shared(type); + + // open it + if (a3003->OpenDevice(devpath, 921600)) + return true; + + // initialize it + a3003->InitDV3003(); + + // set each of the 3 vocoders to the current type + for (uint8_t channel=PKT_CHANNEL0; channelConfigureCodec(channel, type)) + return true; + } + + // add it to the list, according to type + if (Encoding::dstar == type) + dstar_devices.push_back(a3003); + else + dmr_devices.push_back(a3003); + + // finally, toggle the type for the next device + type = (type == Encoding::dstar) ? Encoding::dmr : Encoding::dstar; + } + return false; +} + +void CController::CSVtoSet(const std::string &str, std::set &set, const std::string &delimiters) +{ + auto lastPos = str.find_first_not_of(delimiters, 0); // Skip delimiters at beginning. + auto pos = str.find_first_of(delimiters, lastPos); // Find first non-delimiter. + + while (std::string::npos != pos || std::string::npos != lastPos) + { + std::string element = str.substr(lastPos, pos-lastPos); + set.insert(element); + + lastPos = str.find_first_not_of(delimiters, pos); // Skip delimiters. + pos = str.find_first_of(delimiters, lastPos); // Find next non-delimiter. + } +} diff --git a/Controller.h b/Controller.h new file mode 100644 index 0000000..0481d1e --- /dev/null +++ b/Controller.h @@ -0,0 +1,47 @@ +#pragma once + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include + +#include "DV3003.h" +#include "configure.h" +#include "UnixPacketSocket.h" + +class CController +{ +public: + CController() : keep_running(true) {} + bool InitDevices(); + bool Start(); + void Stop(); + bool IsRunning() { return keep_running; } + +private: + std::atomic keep_running; + std::future future; + std::vector> dmr_devices, dstar_devices; + CUnixPacketClient socket; + + void Processing(); + + void CSVtoSet(const std::string &str, std::set &set, const std::string &delimiters = ","); +}; diff --git a/DV3003.cpp b/DV3003.cpp new file mode 100644 index 0000000..738838e --- /dev/null +++ b/DV3003.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX and John Hays K7VE + * Copyright 2016 by Jeremy McDermond (NH6Z) + * Copyright 2021 by Thomas Early N7TAE + * + * Adapted by K7VE from G4KLX dv3000d + */ + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DV3003.h" + +CDV3003::CDV3003(Encoding t) : type(t), fd(-1) +{ +} + +CDV3003::~CDV3003() +{ + CloseDevice(); +} + +bool CDV3003::checkResponse(SDV3003_Packet &p, uint8_t response) const +{ + if(p.start_byte != PKT_HEADER || p.header.packet_type != PKT_CONTROL || p.field_id != response) + return true; + + return false; +} + +bool CDV3003::IsOpen() const +{ + return fd >= 0; +} + +std::string CDV3003::GetDevicePath() const +{ + return devicepath; +} + +std::string CDV3003::GetVersion() const +{ + return version; +} + +std::string CDV3003::GetProductID() const +{ + return productid; +} + +bool CDV3003::SetBaudRate(int baudrate) +{ + struct termios tty; + + if (tcgetattr(fd, &tty) != 0) { + std::cerr << devicepath << " tcgetattr: " << strerror(errno) << std::endl; + close(fd); + return true; + } + + // Input speed = output speed + cfsetispeed(&tty, B0); + + switch(baudrate) { + case 230400: + cfsetospeed(&tty, B230400); + break; + case 460800: + cfsetospeed(&tty, B460800); + break; + case 921600: + cfsetospeed(&tty, B921600); + break; + default: + std::cerr << devicepath << " unsupported baud rate " << baudrate << std::endl; + close(fd); + return true; + } + + tty.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + tty.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + tty.c_cflag &= ~(CSIZE | CSTOPB | PARENB); + tty.c_cflag |= CS8 | CRTSCTS; + tty.c_oflag &= ~(OPOST); + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 1; + + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + std::cerr << devicepath << " tcsetattr: " << strerror(errno) << std::endl; + close(fd); + return true; + } + return false; +} + +bool CDV3003::OpenDevice(const std::string &ttyname, int baudrate) +{ + fd = open(ttyname.c_str(), O_RDWR | O_NOCTTY | O_SYNC); + if (fd < 0) { + std::cerr << "error when opening " << ttyname << ": " << strerror(errno) << std::endl; + return true; + } + std::cout << "Opened " << ttyname << std::endl; + + if (SetBaudRate(baudrate)) + return true; + + devicepath.assign(ttyname); + + return false; +} + +bool CDV3003::InitDV3003() +{ + char prodId[17]; + char versionstr[49]; + SDV3003_Packet responsePacket, ctrlPacket; + + // ********** hard reset ************* + ctrlPacket.start_byte = PKT_HEADER; + ctrlPacket.header.payload_length = htons(3); + ctrlPacket.header.packet_type = PKT_CONTROL; + ctrlPacket.field_id = PKT_RESET; + ctrlPacket.payload.ctrl.data.paritymode[0] = PKT_PARITYBYTE; + ctrlPacket.payload.ctrl.data.paritymode[1] = 0x3U ^ PKT_RESET ^ PKT_PARITYBYTE; + if (write(fd, &ctrlPacket, packet_size(ctrlPacket)) == -1) { + std::cerr << "InitDV3003: error writing reset packet: " << strerror(errno) << std::endl; + return true; + } + + if (getresponse(responsePacket)) { + std::cerr << "InitDV3003: error receiving response to reset" << std::endl; + return true; + } + + if (checkResponse(responsePacket, PKT_READY)) { + std::cerr << "InitDV3003: invalid response to reset" << std::endl; + return true; + } + + // ********** turn off parity ********* + ctrlPacket.header.payload_length = htons(4); + ctrlPacket.field_id = PKT_PARITYMODE; + ctrlPacket.payload.ctrl.data.paritymode[0] = 0; + ctrlPacket.payload.ctrl.data.paritymode[1] = PKT_PARITYBYTE; + ctrlPacket.payload.ctrl.data.paritymode[2] = 0x4U ^ PKT_PARITYMODE ^ PKT_PARITYBYTE; + if (write(fd, &ctrlPacket, packet_size(ctrlPacket)) == -1) { + std::cerr << "InitDV3003: error writing parity control packet: " << strerror(errno) << std::endl; + return true; + } + + memset(&responsePacket, 0, sizeof(responsePacket)); + if (getresponse(responsePacket)) { + std::cerr << "InitDV3003: error receiving response to parity set" << std::endl; + dump("Parity Ctrl Response Packet", &responsePacket, 4+ntohs(responsePacket.header.payload_length)); + return true; + } + + if (checkResponse(responsePacket, PKT_PARITYMODE)) { + std::cerr << "InitDV3003: invalid response to parity control" << std::endl; + dump("Parity Ctrl Response Packet", &responsePacket, packet_size(responsePacket)); + return true; + } + + // ********* Product ID and Version ************* + ctrlPacket.header.payload_length = htons(1); + ctrlPacket.field_id = PKT_PRODID; + if (write(fd, &ctrlPacket, packet_size(ctrlPacket)) == -1) { + std::cerr << "InitDV3003: error writing product id packet: " << strerror(errno) << std::endl; + return true; + } + + if (getresponse(responsePacket)) { + std::cerr << "InitDV3003: error receiving response to product id request" << std::endl; + return true; + } + + if (checkResponse(responsePacket, PKT_PRODID)) { + std::cerr << "InitDV3003: invalid response to product id query" << std::endl; + return true; + } + strncpy(prodId, responsePacket.payload.ctrl.data.prodid, sizeof(prodId)); + productid.assign(prodId); + + ctrlPacket.field_id = PKT_VERSTRING; + if (write(fd, &ctrlPacket, packet_size(ctrlPacket)) == -1) { + std::cerr << "InitDV3003: error writing version packet: " << strerror(errno) << std::endl; + return true; + } + + if (getresponse(responsePacket)) { + std::cerr << "InitDV3003: error receiving response to version request" << std::endl; + return true; + } + + if (checkResponse(responsePacket, PKT_VERSTRING)) { + std::cerr << "InitDV3003: invalid response to version query" << std::endl; + return true; + } + strncpy(versionstr, responsePacket.payload.ctrl.data.version, sizeof(version)); + version.assign(versionstr); + std::cout << "Found " << prodId << " version " << version << std::endl; + return false; +} + +bool CDV3003::ConfigureCodec(uint8_t pkt_ch, Encoding type) +{ + SDV3003_Packet controlPacket, responsePacket; + const uint8_t dstar[13] { PKT_RATEP, 0x01U, 0x30U, 0x07U, 0x63U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x48U }; + const uint8_t dmr[13] { PKT_RATEP, 0x04U, 0x31U, 0x07U, 0x54U, 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x6FU, 0x48U }; + const uint8_t init[2] { PKT_INIT, 0x3U }; + + + controlPacket.start_byte = PKT_HEADER; + controlPacket.header.payload_length = htons(1 + sizeof(SDV3003_Packet::payload.codec)); + controlPacket.header.packet_type = PKT_CONTROL; + controlPacket.field_id = pkt_ch; + memcpy(controlPacket.payload.codec.ratep, (type == Encoding::dstar) ? dstar : dmr, 13); + memcpy(controlPacket.payload.codec.init, init, 2); + + // write packet + if (0 > write(fd, &controlPacket, packet_size(controlPacket)) ) + { + std::cerr << "error writing codec config packet" << strerror(errno) << std::endl; + return true; + } + + memset(&responsePacket, 0, sizeof(SDV3003_Packet)); + if (getresponse(responsePacket)) { + std::cerr << "error reading codec config response packet" << std::endl; + return true; + } + + if ((ntohs(responsePacket.header.payload_length) != 6) || + (responsePacket.payload.ctrl.data.resp[1] != PKT_RATEP) || + (responsePacket.payload.ctrl.data.resp[2] != 0x00) || + (responsePacket.payload.ctrl.data.resp[3] != PKT_INIT) || + (responsePacket.payload.ctrl.data.resp[4] != 0x00) ) { + std::cerr << "codec config response packet failed" << std::endl; + return true; + }; + std::cout << "channel " << (unsigned int)pkt_ch << " is now configured for " << ((Encoding::dstar == type) ? "D-Star" : "DMR") << std::endl; + return false; +} + +void CDV3003::CloseDevice() +{ + if (fd >= 0) { + close(fd); + fd = -1; + } +} + +bool CDV3003::getresponse(SDV3003_Packet &packet) +{ + ssize_t bytesRead; + + // get the start byte + packet.start_byte = 0U; + const unsigned limit = sizeof(SDV3003_Packet) + 2; + unsigned got = 0; + for (unsigned i = 0U; i < limit; ++i) { + bytesRead = read(fd, &packet.start_byte, 1); + if (bytesRead == -1) { + std::cerr << "CDV3003: Error reading from serial port: " << strerror(errno) << std::endl; + return true; + } + if (bytesRead) + got++; + if (packet.start_byte == PKT_HEADER) + break; + } + if (packet.start_byte != PKT_HEADER) { + std::cerr << "CDV3003: Couldn't find start byte in serial data: tried " << limit << " times, got " << got << " bytes" << std::endl; + return true; + } + + // get the packet size and type (three bytes) + ssize_t bytesLeft = sizeof(packet.header); + ssize_t total = bytesLeft; + while (bytesLeft > 0) { + bytesRead = read(fd, ((uint8_t *) &packet.header) + total - bytesLeft, bytesLeft); + if(bytesRead == -1) { + std::cout << "AMBEserver: Couldn't read serial data header" << std::endl; + return true; + } + bytesLeft -= bytesRead; + } + + total = bytesLeft = ntohs(packet.header.payload_length); + if (bytesLeft > 1 + int(sizeof(packet.payload))) { + std::cout << "AMBEserver: Serial payload exceeds buffer size: " << int(bytesLeft) << std::endl; + return true; + } + + while (bytesLeft > 0) { + bytesRead = read(fd, ((uint8_t *) &packet.field_id) + total - bytesLeft, bytesLeft); + if (bytesRead == -1) { + std::cerr << "AMBEserver: Couldn't read payload: " << strerror(errno) << std::endl; + return true; + } + + bytesLeft -= bytesRead; + } + + return false; +} + +bool CDV3003::SendAudio(const uint8_t channel, const int16_t *audio) const +{ + // Create Audio packet based on input int8_ts + SDV3003_Packet p; + p.start_byte = PKT_HEADER; + const uint16_t len = 323; + p.header.payload_length = htons(len); + p.header.packet_type = PKT_SPEECH; + p.field_id = channel; + p.payload.audio.speechd = 0x0U; + p.payload.audio.num_samples = 160U; + for (int i=0; i<160; i++) + p.payload.audio.samples[i] = htons(audio[i]); + + // send audio packet to DV3000 + int size = packet_size(p); + if (write(fd, &p, size) != size) { + std::cerr << "Error sending audio packet" << std::endl; + return true; + } + return false; +} + +bool CDV3003::GetData(uint8_t *data) +{ + SDV3003_Packet p; + // read data packet from DV3000 + p.start_byte = 0U; + if (getresponse(p)) + return true; + if (p.start_byte!=PKT_HEADER || htons(p.header.payload_length)!=12 || + p.header.packet_type!=PKT_CHANNEL || p.payload.ambe.chand!=1U || + p.payload.ambe.num_bits!=72U) { + std::cerr << "Error receiving audio packet response" << std::endl; + dump("Received AMBE", &p, packet_size(p)); + return true; + } + + // copy it to the output + memcpy(data, p.payload.ambe.data, 9); + + return false; +} + +bool CDV3003::SendData(const uint8_t channel, const uint8_t *data) const +{ + // Create data packet + SDV3003_Packet p; + p.start_byte = PKT_HEADER; + p.header.payload_length = htons(12); + p.header.packet_type = PKT_CHANNEL; + p.field_id = channel; + p.payload.ambe.num_bits = 72U; + p.payload.ambe.chand = 0x1U; + memcpy(p.payload.ambe.data, data, 9); + + // send data packet to DV3000 + int size = packet_size(p); + if (write(fd, &p, size) != size) { + std::cerr << "SendData: error sending data packet" << std::endl; + dump("Received Data", &p, size); + return true; + } + return false; +} + +bool CDV3003::GetAudio(int16_t *audio) +{ + SDV3003_Packet p; + // read audio packet from DV3000 + p.start_byte = 0U; + if (getresponse(p)) + return true; + if (p.start_byte!=PKT_HEADER || htons(p.header.payload_length)!=322 || + p.header.packet_type!=PKT_SPEECH || p.payload.audio.speechd!=0U || + p.payload.audio.num_samples!=160U) { + std::cerr << "GetAudio: unexpected audio packet response" << std::endl; + int size = packet_size(p); + dump("Received Audio", &p, size); + return true; + } + + for (int i=0; i<160; i++) + audio[i] = ntohs(p.payload.audio.samples[i]); + + return false; +} + +void CDV3003::dump(const char *title, void *pointer, int length) const +{ + assert(title != NULL); + assert(pointer != NULL); + + const uint8_t *data = (const uint8_t *)pointer; + + std::cout << title << std::endl; + + unsigned int offset = 0U; + + while (length > 0) { + + unsigned int bytes = (length > 16) ? 16U : length; + + for (unsigned i = 0U; i < bytes; i++) { + if (i) + std::cout << " "; + std::cout << std::hex << std::setw(2) << std::right << std::setfill('0') << int(data[offset + i]); + } + + for (unsigned int i = bytes; i < 16U; i++) + std::cout << " "; + + std::cout << " *"; + + for (unsigned i = 0U; i < bytes; i++) { + uint8_t c = data[offset + i]; + + if (::isprint(c)) + std::cout << c; + else + std::cout << '.'; + } + + std::cout << '*' << std::endl; + + offset += 16U; + + if (length >= 16) + length -= 16; + else + length = 0; + } +} diff --git a/DV3003.h b/DV3003.h new file mode 100644 index 0000000..52f1fe2 --- /dev/null +++ b/DV3003.h @@ -0,0 +1,115 @@ +#pragma once + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include + +#define USB3XXX_MAXPACKETSIZE 1024 // must be multiple of 64 + +#define PKT_HEADER 0x61 + +#define PKT_CONTROL 0x00 +#define PKT_CHANNEL 0x01 +#define PKT_SPEECH 0x02 + +#define PKT_SPEECHD 0x00 +#define PKT_CHAND 0x01 +#define PKT_RATET 0x09 +#define PKT_INIT 0x0b +#define PKT_PRODID 0x30 +#define PKT_VERSTRING 0x31 +#define PKT_PARITYBYTE 0x2F +#define PKT_RESET 0x33 +#define PKT_READY 0x39 +#define PKT_CHANNEL0 0x40 +#define PKT_CHANNEL1 0x41 +#define PKT_CHANNEL2 0x42 +#define PKT_PARITYMODE 0x3F +#define PKT_ECMODE 0x05 +#define PKT_DCMODE 0x06 +#define PKT_COMPAND 0x32 +#define PKT_RATEP 0x0A +#define PKT_CHANFMT 0x15 +#define PKT_SPCHFMT 0x16 +#define PKT_GAIN 0x4B + +#define packet_size(a) int(1 + sizeof((a).header) + ntohs((a).header.payload_length)) + +#pragma pack(push, 1) +struct dv3003_packet { + uint8_t start_byte; + struct { + uint16_t payload_length; + uint8_t packet_type; + } header; + uint8_t field_id; // for audio and ambe, this is channel# 0x40U, 0x41U or 0x42U + union { + struct { + union { + char prodid[16]; + uint8_t paritymode[3]; + char version[48]; + uint8_t resp[5]; // for the codec config response, RATEP and INIT + } data; + } ctrl; + struct { + uint8_t ratep[13]; + uint8_t init[2]; + } codec; + struct { + uint8_t speechd; // 0 + uint8_t num_samples; // 160 + int16_t samples[160]; // 4 + 1 + 2 + 320 = 327 byte + } audio; + struct { + uint8_t chand; // 1 + uint8_t num_bits; // 72 (9 bytes) + uint8_t data[9]; // 4 + 1 + 2 + 9 = 16 bytes + } ambe; + } payload; +}; +#pragma pack(pop) + +using SDV3003_Packet = struct dv3003_packet; +enum class Encoding { dstar, dmr }; + +class CDV3003 { +public: + CDV3003(Encoding t); + ~CDV3003(); + bool OpenDevice(const std::string &device, int baudrate); + bool InitDV3003(); + bool ConfigureCodec(uint8_t pkt_ch, Encoding type); + bool SendAudio(const uint8_t channel, const int16_t *audio) const; + bool GetData(uint8_t *data); + bool SendData(const uint8_t channel, const uint8_t *data) const; + bool GetAudio(int16_t *audio); + void CloseDevice(); + bool IsOpen() const; + void dump(const char *title, void *data, int length) const; + std::string GetDevicePath() const; + std::string GetProductID() const; + std::string GetVersion() const; +private: + const Encoding type; + int fd; + std::string devicepath, productid, version; + bool SetBaudRate(int baudrate); + bool getresponse(SDV3003_Packet &packet); + bool checkResponse(SDV3003_Packet &responsePacket, uint8_t response) const; +}; diff --git a/README.md b/README.md index 89baebe..41e28bc 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# tcd \ No newline at end of file +# TCd + +TCd - A hybrid vocoder that uses both DVSI hardware and open source software to provide a universal transcoding service designed for use by a multi-mode reflector. diff --git a/TranscoderPacket.cpp b/TranscoderPacket.cpp new file mode 100644 index 0000000..090a58f --- /dev/null +++ b/TranscoderPacket.cpp @@ -0,0 +1,115 @@ + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "TranscoderPacket.h" + +CTranscoderPacket::CTranscoderPacket() : module(' '), codec_in(ECodecType::none), dstar_set(false), dmr_set(false), m17_set(false) +{ +} + +char CTranscoderPacket::GetModule() const +{ + return module; +} + +void CTranscoderPacket::SetModule(char mod) +{ + module = mod; +} + +const uint8_t *CTranscoderPacket::GetDStarData() +{ + return data.dstar; +} + +const uint8_t *CTranscoderPacket::GetDMRData() +{ + return data.dmr; +} + +const uint8_t *CTranscoderPacket::GetM17Data() +{ + return data.m17; +} + +void CTranscoderPacket::SetDStarData(const uint8_t *dstar) +{ + memcpy(data.dstar, dstar, 9); + dstar_set = true; +} +void CTranscoderPacket::SetDMRData(const uint8_t *dmr ) +{ + memcpy(data.dmr, dmr, 9); + dmr_set = true; +} + +void CTranscoderPacket::SetM17Data(const uint8_t *m17, bool is_3200) +{ + memcpy(data.m17, m17, is_3200 ? 16 : 8); + m17_set = true; + m17_is_3200 = is_3200; +} + +void CTranscoderPacket::SetCodecIn(ECodecType type, uint8_t *data) +{ + switch (type) { + case ECodecType::dstar: + SetDStarData(data); + break; + case ECodecType::dmr: + SetDMRData(data); + break; + case ECodecType::m17_1600: + SetM17Data(data, false); + break; + case ECodecType::m17_3200: + SetM17Data(data, true); + true; + } + if (type != ECodecType::none) + codec_in = type; +} + +ECodecType CTranscoderPacket::GetCodecIn() const +{ + return codec_in; +} + +bool CTranscoderPacket::DStarIsSet() const +{ + return dstar_set; +} + +bool CTranscoderPacket::DMRIsSet() const +{ + return dmr_set; +} + +bool CTranscoderPacket::M17IsSet() const +{ + return m17_set; +} + +bool CTranscoderPacket::M17Is3200() const +{ + return m17_is_3200; +} + +bool CTranscoderPacket::AllAreSet() const +{ + return (dstar_set && dmr_set && m17_set); +} diff --git a/TranscoderPacket.h b/TranscoderPacket.h new file mode 100644 index 0000000..ece460c --- /dev/null +++ b/TranscoderPacket.h @@ -0,0 +1,65 @@ +#pragma once + + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include + +using SCodecData = struct codecdata_tag { + uint8_t dstar[9]; + uint8_t dmr[9]; + uint8_t m17[16]; +}; + +enum class ECodecType { none, dstar, dmr, m17_1600, m17_3200 }; + +class CTranscoderPacket +{ +public: + // constructor + CTranscoderPacket(); + + // this packet's refector module; + char GetModule() const; + void SetModule(char mod); + + // codec + const uint8_t *GetDStarData(); + const uint8_t *GetDMRData(); + const uint8_t *GetM17Data(); + void SetDStarData(const uint8_t *dstar); + void SetDMRData(const uint8_t *dmr ); + void SetM17Data(const uint8_t *m17, bool is_3200); + + // first time load + void SetCodecIn(ECodecType type, uint8_t *data); + + // state of packet + ECodecType GetCodecIn() const; + bool DStarIsSet() const; + bool DMRIsSet() const; + bool M17IsSet() const; + bool M17Is3200() const; + bool AllAreSet() const; + +private: + char module; + ECodecType codec_in; + bool m17_is_3200, dstar_set, dmr_set, m17_set; + SCodecData data; +}; diff --git a/UnixPacketSocket.cpp b/UnixPacketSocket.cpp new file mode 100644 index 0000000..6955697 --- /dev/null +++ b/UnixPacketSocket.cpp @@ -0,0 +1,219 @@ + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include +#include +#include + +#include "UnixPacketSocket.h" + +CUnixPacket::CUnixPacket() : m_fd(-1), m_host(NULL) {} + +bool CUnixPacket::Receive(std::vector &Buffer, unsigned timeout) +{ + // socket valid ? + if ( 0 > m_fd ) + return false; + + // control socket + fd_set FdSet; + FD_ZERO(&FdSet); + FD_SET(m_fd, &FdSet); + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + auto rval = select(m_fd + 1, &FdSet, 0, 0, &tv); + if (rval < 0) + { + std::cerr << "select error on Unix socket " << m_name << ": " << strerror(errno) << std::endl; + } + + if (rval > 0) + { + uint8_t buf[USB3XXX_MAXPACKETSIZE]; + auto len = read(m_fd, buf, USB3XXX_MAXPACKETSIZE); + if (len <= 0) + return true; + Buffer.resize(len); + memcpy(Buffer.data(), buf, len); + return false; + } + + Restart(); + return true; +} + + +bool CUnixPacket::Write(const void *buffer, const ssize_t size) +{ + if (0 > m_fd) + return true; + ssize_t written = write(m_fd, buffer, size); + if (written != size) + { + if (-1 == written) + { + std::cerr << "Write error on '" << m_name << "': " << strerror(errno) << std::endl; + } + else + { + std::cout << "Write error on '" << m_name << "': Only wrote " << written << " of " << size << " bytes" << std::endl; + } + return Restart(); + } + return false; +} + +bool CUnixPacket::Restart() +{ + if (! m_host->IsRunning()) + return true; + std::cout << "Restarting '" << m_name << "'... " << std::endl; + Close(); + std::string name(m_name); + return Open(name.c_str(), m_host); +} + +int CUnixPacket::GetFD() +{ + return m_fd; +} + +CUnixPacketServer::CUnixPacketServer() : m_server(-1) {} + +CUnixPacketServer::~CUnixPacketServer() +{ + Close(); +} + +bool CUnixPacketServer::Open(const char *name, CController *host) +{ + m_server = socket(AF_UNIX, SOCK_SEQPACKET, 0); + m_host = host; + if (m_server < 0) + { + std::cerr << "Cannot open '" << name << "' socket: " << strerror(errno) << std::endl; + return true; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path+1, name, strlen(name)); + if (-1 == bind(m_server, (struct sockaddr *)&addr, sizeof(addr))) + { + std::cerr << "Cannot bind '" << name << "' socket: " << strerror(errno) << std::endl; + Close(); + return true; + } + + if (-1 == listen(m_server, 1)) + { + std::cerr << "Cannot listen on '" << name << "' socket: " << strerror(errno) << std::endl; + Close(); + return true; + } + + m_fd = accept(m_server, nullptr, 0); + if (m_fd < 0) + { + std::cerr << "Cannot accept on '" << name << "' socket: " << strerror(errno) << std::endl; + Close(); + return true; + } + + strncpy(m_name, name, 108); + return false; +} + +void CUnixPacketServer::Close() +{ + if (m_server >= 0) + { + close(m_server); + m_server = -1; + } + if (m_fd >= 0) + { + close(m_fd); + m_fd = -1; + } +} + +CUnixPacketClient::~CUnixPacketClient() +{ + Close(); +} + +bool CUnixPacketClient::Open(const char *name, CController *host) +{ + m_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (m_fd < 0) + { + std::cerr << "Cannot open unix client socket " << name << std::endl; + return true; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path+1, name, strlen(name)); + int rval = -1; + int tries = 0; + while (rval < 0) + { + rval = connect(m_fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rval < 0) + { + if (ECONNREFUSED == errno) + { + if (0 == tries++ % 20) + std::cout << "Waiting for " << name << " server to start..." << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + else + { + std::cerr << "Cannot connect '" << name << "' socket: " << strerror(errno) << std::endl; + Close(); + return true; + } + } + if (! m_host->IsRunning()) + { + Close(); + return true; + } + } + + m_host = host; + strncpy(m_name, name, 108); + return false; +} + +void CUnixPacketClient::Close() +{ + if (m_fd >= 0) + { + close(m_fd); + m_fd = -1; + } +} diff --git a/UnixPacketSocket.h b/UnixPacketSocket.h new file mode 100644 index 0000000..75341f0 --- /dev/null +++ b/UnixPacketSocket.h @@ -0,0 +1,58 @@ +#pragma once + + +// tcd - a hybid transcoder using DVSI hardware and Codec2 software +// Copyright © 2021 Thomas A. Early N7TAE + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include + +#include "Controller.h" + +class CUnixPacket +{ +public: + CUnixPacket(); + virtual bool Open(const char *name, CController *host) = 0; + virtual void Close() = 0; + bool Write(const void *buffer, const ssize_t size); + bool Receive(std::vector &buf, unsigned timeout); + int GetFD(); +protected: + bool Restart(); + int m_fd; + CController *m_host; + char m_name[108]; +}; + +class CUnixPacketServer : public CUnixPacket +{ +public: + CUnixPacketServer(); + ~CUnixPacketServer(); + bool Open(const char *name, CController *host); + void Close(); +protected: + int m_server; +}; + +class CUnixPacketClient : public CUnixPacket +{ +public: + ~CUnixPacketClient(); + bool Open(const char *name, CController *host); + void Close(); +};