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();
+};