diff --git a/.gitignore b/.gitignore index dc59682..d900137 100644 --- a/.gitignore +++ b/.gitignore @@ -29,9 +29,5 @@ # Visual Studio .vscode -#files -configure.h -configure.mk - # Executables tcd diff --git a/Configure.cpp b/Configure.cpp new file mode 100644 index 0000000..78d00d8 --- /dev/null +++ b/Configure.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2023 by 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "Configure.h" + +// ini file keywords +#define USRPTXGAIN "UsrpTxGain" +#define USRPRXGAIN "UsrpRxGain" +#define DMRGAININ "DmrYsfGainIn" +#define DMRGAINOUT "DmrYsfGainOut" +#define DSTARGAININ "DStarGainIn" +#define DSTARGAINOUT "DStarGainOut" +#define TRANSCODED "Transcoded" + +static inline void split(const std::string &s, char delim, std::vector &v) +{ + std::istringstream iss(s); + std::string item; + while (std::getline(iss, item, delim)) + v.push_back(item); +} + +// trim from start (in place) +static inline void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} + +// trim from end (in place) +static inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +// trim from both ends (in place) +static inline void trim(std::string &s) { + ltrim(s); + rtrim(s); +} + +bool CConfigure::ReadData(const std::string &path) +// returns true on failure +{ + std::string modstmp; + + std::ifstream cfgfile(path.c_str(), std::ifstream::in); + if (! cfgfile.is_open()) { + std::cerr << "ERROR: '" << path << "' was not found!" << std::endl; + return true; + } + + std::string line; + while (std::getline(cfgfile, line)) + { + trim(line); + if (3 > line.size()) + continue; // can't be anything + if ('#' == line.at(0)) + continue; // skip comments + + std::vector tokens; + split(line, '=', tokens); + // check value for end-of-line comment + if (2 > tokens.size()) + { + std::cout << "WARNING: '" << line << "' does not contain an equal sign, skipping" << std::endl; + continue; + } + auto pos = tokens[1].find('#'); + if (std::string::npos != pos) + { + tokens[1].assign(tokens[1].substr(0, pos)); + rtrim(tokens[1]); // whitespace between the value and the end-of-line comment + } + // trim whitespace from around the '=' + rtrim(tokens[0]); + ltrim(tokens[1]); + const std::string key(tokens[0]); + const std::string value(tokens[1]); + if (key.empty() || value.empty()) + { + std::cout << "WARNING: missing key or value: '" << line << "'" << std::endl; + continue; + } + if (0 == key.compare(TRANSCODED)) + modstmp.assign(value); + else if (0 == key.compare(DSTARGAININ)) + dstar_in = getSigned(key, value); + else if (0 == key.compare(DSTARGAINOUT)) + dstar_out = getSigned(key, value); + else if (0 == key.compare(DMRGAININ)) + dmr_in = getSigned(key, value); + else if (0 == key.compare(DMRGAINOUT)) + dmr_out = getSigned(key, value); + else if (0 == key.compare(USRPTXGAIN)) + usrp_tx = getSigned(key, value); + else if (0 == key.compare(USRPRXGAIN)) + usrp_rx = getSigned(key, value); + else + badParam(key); + } + cfgfile.close(); + + for (auto c : modstmp) + { + if (isalpha(c)) + { + if (islower(c)) + c = toupper(c); + if (std::string::npos == tcmods.find(c)) + tcmods.append(1, c); + } + } + if (tcmods.empty()) + { + std::cerr << "ERROR: no identifable module letters in '" << modstmp << "'. Halt." << std::endl; + return true; + } + + std::cout << TRANSCODED << " = " << tcmods << std::endl; + std::cout << DSTARGAININ << " = " << dstar_in << std::endl; + std::cout << DSTARGAINOUT << " = " << dstar_out << std::endl; + std::cout << DMRGAININ << " = " << dmr_in << std::endl; + std::cout << DMRGAINOUT << " = " << dmr_out << std::endl; + std::cout << USRPTXGAIN << " = " << usrp_tx << std::endl; + std::cout << USRPRXGAIN << " = " << usrp_rx << std::endl; + + return false; +} + +int CConfigure::getSigned(const std::string &key, const std::string &value) const +{ + auto i = std::stoi(value.c_str()); + if (i < -24) + { + std::cout << "WARNING: " << key << " = " << value << " is too low. Limit to -24!" << std::endl; + i = -24; + } + else if (i > 24) + { + std::cout << "WARNING: " << key << " = " << value << " is too high. Limit to 24!" << std::endl; + i = 24; + } + return i; +} + +void CConfigure::badParam(const std::string &key) const +{ + std::cout << "WARNING: Unexpected parameter: '" << key << "'" << std::endl; +} + +int CConfigure::GetGain(EGainType gt) const +{ + switch (gt) + { + case EGainType::dmrin: return dmr_in; + case EGainType::dmrout: return dmr_out; + case EGainType::dstarin: return dstar_in; + case EGainType::dstarout: return dstar_out; + case EGainType::usrptx: return usrp_tx; + case EGainType::usrprx: return usrp_rx; + default: return 0; + } +} diff --git a/Configure.h b/Configure.h new file mode 100644 index 0000000..e553f38 --- /dev/null +++ b/Configure.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 by 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include + +enum class EGainType { dmrin, dmrout, dstarin, dstarout, usrptx, usrprx }; + +#define IS_TRUE(a) ((a)=='t' || (a)=='T' || (a)=='1') + +class CConfigure +{ +public: + bool ReadData(const std::string &path); + int GetGain(EGainType gt) const; + std::string GetTCMods(void) const { return tcmods; } + +private: + // CFGDATA data; + std::string tcmods; + int dstar_in, dstar_out, dmr_in, dmr_out, usrp_tx, usrp_rx; + + int getSigned(const std::string &key, const std::string &value) const; + void badParam(const std::string &key) const; +}; diff --git a/Controller.cpp b/Controller.cpp index e39bd92..ad8cf95 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -28,31 +28,24 @@ #include "TranscoderPacket.h" #include "Controller.h" +#include "Configure.h" +extern CConfigure g_Conf; -//#define AMBE_GAIN 16 //Encoder gain in dB (I use 16 here) -//#define AMBE2_GAIN -24 //Encoder gain in dB (I use -24 here) -#define USRP_RXGAIN -6 -#define USRP_TXGAIN 3 - -int16_t calcGainVal(float db) +int32_t CController::calcNumerator(int32_t db) const { - float ratio = powf(10.0, (db/20.0)); - - if(db < 0){ - ratio = (1/ratio) * (-1); - } - - return (int16_t)roundf(ratio); + float num = 256.0f * powf(10.0f, (float(db)/20.0f)); + + return int32_t(roundf(num)); } CController::CController() : keep_running(true) {} bool CController::Start() { - usrp_rxgain = calcGainVal(USRP_RXGAIN); - usrp_txgain = calcGainVal(USRP_TXGAIN); - + usrp_rx_num = calcNumerator(g_Conf.GetGain(EGainType::usrprx)); + usrp_tx_num = calcNumerator(g_Conf.GetGain(EGainType::usrptx)); + if (InitVocoders() || reader.Open(REF2TC)) { keep_running = false; @@ -130,7 +123,7 @@ bool CController::DiscoverFtdiDevices(std::list(new CCodec2(false)); @@ -196,7 +189,8 @@ bool CController::InitVocoders() dstar_device = std::unique_ptr(new CDV3000(Encoding::dstar)); #ifdef USE_SW_AMBE2 md380_init(); - ambe_gain = calcGainVal(DMR_IN_GAIN); + ambe_in_num = calcNumerator(g_Conf.GetGain(EGainType::dmrin)); + ambe_out_num = calcNumerator(g_Conf.GetGain(EGainType::dmrout)); #else dmrsf_device = std::unique_ptr(new CDV3000(Encoding::dmrsf)); #endif @@ -210,10 +204,10 @@ bool CController::InitVocoders() dmrsf_device = std::unique_ptr(new CDV3003(Encoding::dmrsf)); #endif } - + if (dstar_device) { - if (dstar_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype, DSTAR_IN_GAIN, DSTAR_OUT_GAIN)) + if (dstar_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype, int8_t(g_Conf.GetGain(EGainType::dstarin)), int8_t(g_Conf.GetGain(EGainType::dstarout)))) return true; deviceset.pop_front(); } @@ -225,7 +219,7 @@ bool CController::InitVocoders() #ifndef USE_SW_AMBE2 if (dmrsf_device) { - if (dmrsf_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype, DMR_IN_GAIN, DMR_OUT_GAIN)) + if (dmrsf_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype, int8_t(g_Conf.GetGain(EGainType::dmrin)), int8_t(g_Conf.GetGain(EGainType::dmrout)))) return true; deviceset.pop_front(); } @@ -239,7 +233,7 @@ bool CController::InitVocoders() // and start them (or it) up! dstar_device->Start(); - + #ifndef USE_SW_AMBE2 dmrsf_device->Start(); #endif @@ -331,7 +325,7 @@ void CController::Codec2toAudio(std::shared_ptr packet) { uint8_t ambe2[9]; uint8_t imbe[11]; - + if (packet->IsSecond()) { if (packet->GetCodecIn() == ECodecType::c2_1600) @@ -415,24 +409,21 @@ void CController::ProcessC2Thread() #ifdef USE_SW_AMBE2 void CController::AudiotoSWAMBE2(std::shared_ptr packet) { - const auto m = packet->GetModule(); uint8_t ambe2[9]; - int16_t tmp[160]; const int16_t *p = packet->GetAudioSamples(); - const uint32_t g = abs(ambe_gain); - - for(int i = 0; i < 160; ++i){ - if(ambe_gain < 0){ - tmp[i] = p[i] / g; - } - else{ - tmp[i] = p[i] * g; - } + + if (ambe_in_num != 256) + { + int16_t tmp[160]; + for(int i = 0; i < 160; ++i) + tmp[i] = int16_t((p[i] * ambe_in_num) >> 8); + md380_encode_fec(ambe2, tmp); } - - md380_encode_fec(ambe2, tmp); + else + md380_encode_fec(ambe2, p); + packet->SetDMRData(ambe2); - + // we might be all done... send_mux.lock(); if (packet->AllCodecsAreSet() && packet->HasNotBeenSent()) SendToReflector(packet); @@ -441,9 +432,15 @@ void CController::AudiotoSWAMBE2(std::shared_ptr packet) void CController::SWAMBE2toAudio(std::shared_ptr packet) { - int16_t tmp[160] = {0}; + int16_t tmp[160]; md380_decode_fec(packet->GetDMRData(), tmp); + if (ambe_out_num != 256) + { + for (int i=0; i<160; i++) + tmp[i] = (tmp[i] * ambe_out_num) >> 8; + } packet->SetAudioSamples(tmp, false); + dstar_device->AddPacket(packet); codec2_queue.push(packet); imbe_queue.push(packet); @@ -488,18 +485,18 @@ void CController::AudiotoIMBE(std::shared_ptr packet) void CController::IMBEtoAudio(std::shared_ptr packet) { - int16_t tmp[160] = {0}; + int16_t tmp[160] = { 0 }; p25vocoder.decode_4400(tmp, (uint8_t*)packet->GetP25Data()); packet->SetAudioSamples(tmp, false); dstar_device->AddPacket(packet); codec2_queue.push(packet); - + #ifdef USE_SW_AMBE2 swambe2_queue.push(packet); #else dmrsf_device->AddPacket(packet); #endif - + usrp_queue.push(packet); } @@ -528,21 +525,19 @@ void CController::ProcessIMBEThread() void CController::AudiotoUSRP(std::shared_ptr packet) { - int16_t tmp[160]; const int16_t *p = packet->GetAudioSamples(); - const uint32_t g = abs(usrp_txgain); - - for(int i = 0; i < 160; ++i){ - if(usrp_txgain < 0){ - tmp[i] = p[i] / g; - } - else{ - tmp[i] = p[i] * g; - } + + if (usrp_tx_num != 256) + { + int16_t tmp[160]; + for(int i = 0; i < 160; ++i) + tmp[i] = int16_t((p[i] * usrp_tx_num) >> 8); + packet->SetUSRPData(tmp); } - - packet->SetUSRPData(tmp); - + else + packet->SetUSRPData(p); + + // we might be all done... send_mux.lock(); if (packet->AllCodecsAreSet() && packet->HasNotBeenSent()) SendToReflector(packet); @@ -551,30 +546,27 @@ void CController::AudiotoUSRP(std::shared_ptr packet) void CController::USRPtoAudio(std::shared_ptr packet) { - int16_t tmp[160]; const int16_t *p = packet->GetUSRPData(); - const uint32_t g = abs(usrp_rxgain); - - for(int i = 0; i < 160; ++i){ - if(usrp_rxgain < 0){ - tmp[i] = p[i] / g; - } - else{ - tmp[i] = p[i] * g; - } + + if (usrp_rx_num != 256) + { + int16_t tmp[160]; + for(int i = 0; i < 160; ++i) + tmp[i] = int16_t((p[i] * usrp_rx_num) >> 8); + packet->SetAudioSamples(tmp, false); } - - //packet->SetAudioSamples(packet->GetUSRPData(), false); - packet->SetAudioSamples(tmp, false); + else + packet->SetAudioSamples(p, false); + dstar_device->AddPacket(packet); codec2_queue.push(packet); - + #ifdef USE_SW_AMBE2 swambe2_queue.push(packet); #else dmrsf_device->AddPacket(packet); #endif - + imbe_queue.push(packet); } diff --git a/Controller.h b/Controller.h index 5b2ae04..0b65473 100644 --- a/Controller.h +++ b/Controller.h @@ -30,7 +30,6 @@ #include "DV3000.h" #include "DV3003.h" #include "UnixDgramSocket.h" -#include "configure.h" class CController { @@ -55,21 +54,20 @@ protected: std::unique_ptr dstar_device, dmrsf_device; CPacketQueue codec2_queue; - + CPacketQueue imbe_queue; CPacketQueue usrp_queue; std::mutex send_mux; - int16_t ambe_gain; - int16_t usrp_rxgain; - int16_t usrp_txgain; + int32_t ambe_in_num, ambe_out_num, usrp_rx_num, usrp_tx_num; imbe_vocoder p25vocoder; + int32_t calcNumerator(int32_t db) const; bool DiscoverFtdiDevices(std::list> &found); bool InitVocoders(); // processing threads void ReadReflectorThread(); void ProcessC2Thread(); - + void ProcessIMBEThread(); void ProcessUSRPThread(); void Codec2toAudio(std::shared_ptr packet); diff --git a/DV3000.cpp b/DV3000.cpp index 687c945..e707273 100644 --- a/DV3000.cpp +++ b/DV3000.cpp @@ -33,10 +33,10 @@ #include #include "DV3000.h" -#include "configure.h" +#include "Configure.h" #include "Controller.h" -extern CController Controller; +extern CController g_Cont; CDV3000::CDV3000(Encoding t) : CDVDevice(t) {} @@ -144,15 +144,15 @@ void CDV3000::ProcessPacket(const SDV_Packet &p) } if (Encoding::dstar == type) // is this a DMR or a DStar device? { - Controller.dstar_mux.lock(); - Controller.RouteDstPacket(packet); - Controller.dstar_mux.unlock(); + g_Cont.dstar_mux.lock(); + g_Cont.RouteDstPacket(packet); + g_Cont.dstar_mux.unlock(); } else { - Controller.dmrst_mux.lock(); - Controller.RouteDmrPacket(packet); - Controller.dmrst_mux.unlock(); + g_Cont.dmrst_mux.lock(); + g_Cont.RouteDmrPacket(packet); + g_Cont.dmrst_mux.unlock(); } } } diff --git a/DV3003.cpp b/DV3003.cpp index 6a59668..b1dec1e 100644 --- a/DV3003.cpp +++ b/DV3003.cpp @@ -33,10 +33,10 @@ #include #include "DV3003.h" -#include "configure.h" +#include "Configure.h" #include "Controller.h" -extern CController Controller; +extern CController g_Cont; CDV3003::CDV3003(Encoding t) : CDVDevice(t) {} @@ -148,15 +148,15 @@ void CDV3003::ProcessPacket(const SDV_Packet &p) } if (Encoding::dstar == type) // is this a DMR or a DStar device? { - Controller.dstar_mux.lock(); - Controller.RouteDstPacket(packet); - Controller.dstar_mux.unlock(); + g_Cont.dstar_mux.lock(); + g_Cont.RouteDstPacket(packet); + g_Cont.dstar_mux.unlock(); } else { - Controller.dmrst_mux.lock(); - Controller.RouteDmrPacket(packet); - Controller.dmrst_mux.unlock(); + g_Cont.dmrst_mux.lock(); + g_Cont.RouteDmrPacket(packet); + g_Cont.dmrst_mux.unlock(); } } } diff --git a/DVSIDevice.cpp b/DVSIDevice.cpp index ecb826e..61409ca 100644 --- a/DVSIDevice.cpp +++ b/DVSIDevice.cpp @@ -34,10 +34,9 @@ #include #include "DVSIDevice.h" -#include "configure.h" -#include "Controller.h" +#include "Configure.h" -extern CController Controller; +extern CConfigure g_Conf; CDVDevice::CDVDevice(Encoding t) : type(t), ftHandle(nullptr), buffer_depth(0), keep_running(true) { @@ -565,7 +564,7 @@ void CDVDevice::dump(const char *title, const void *pointer, int length) const void CDVDevice::FeedDevice() { - const std::string modules(TRANSCODED_MODULES); + const std::string modules(g_Conf.GetTCMods()); const auto n = modules.size(); while (keep_running) { diff --git a/Main.cpp b/Main.cpp index d27f7f7..af9a432 100644 --- a/Main.cpp +++ b/Main.cpp @@ -18,20 +18,31 @@ #include #include "Controller.h" +#include "Configure.h" -// the global controller object -CController Controller; +// the global objects +CConfigure g_Conf; +CController g_Cont; -int main() +int main(int argc, char *argv[]) { - if (Controller.Start()) + if (2 != argc) + { + std::cerr << "ERROR: Usage: " << argv[0] << " PATHTOINIFILE" << std::endl; return EXIT_FAILURE; + } - std::cout << "Hybrid Transcoder version 0.0.5 successfully started" << std::endl; + if (g_Conf.ReadData(argv[1])) + return EXIT_FAILURE; + + if (g_Cont.Start()) + return EXIT_FAILURE; + + std::cout << "Hybrid Transcoder version 0.1.0 successfully started" << std::endl; pause(); - Controller.Stop(); + g_Cont.Stop(); return EXIT_SUCCESS; } diff --git a/Makefile b/Makefile index a616efb..b67209f 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,6 @@ #Copyright (C) 2021 by Thomas A. Early, N7TAE -include configure.mk - -# If you are going to change this path, you will -# need to update the systemd service script -BINDIR = /usr/local/bin - -swambe2 = false +include tcd.mk GCC = g++ @@ -49,7 +43,7 @@ clean : # The install and uninstall targets need to be run by root install : $(EXE) cp $(EXE) $(BINDIR) - cp systemd/$(EXE).service /etc/systemd/system/ + cp $(EXE).service /etc/systemd/system/ systemctl enable $(EXE) systemctl daemon-reload systemctl start $(EXE) diff --git a/README.md b/README.md index 61c23e3..d8a34ba 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,57 @@ tcd is a hybrid digital voice transcoder for ham radio used by the new URF refle ## Introduction -This will build a new kind of hybrid transcoder that uses AMBE DVSI-based hardware for vocoding digital voice streams used in DStar/DMR/YSF *and* David Rowe's open-source Codec2 used in M17. TCd is optimized for performance by using a highly multi-threaded design that incorporates blocking I/O to make it as efficient as possible. +This will build a new kind of hybrid transcoder that uses AMBE DVSI-based hardware for vocoding digital voice streams used in DStar/DMR/YSF *and* David Rowe's open-source Codec2 used in M17 as well as the open-source P25 vocoder, IMBE. -This is the only transcoder that will work with the [URF reflector](https://github.com/n7tae/urfd). - -The imbe_vocoder library is required for P25 and can be found here: - -https://github.com/nostar/imbe_vocoder - -To use md380_vocoder along with a single DV Dongle on an ARM platform (like RPi) change the line 'swambe2 = false' to 'swambe2 = true' in the Makefile. The md380_vocoder library can be found here: - -https://github.com/nostar/md380_vocoder +This is the only transcoder that will work with the [URF reflector](https://github.com/nostar/urfd). This software is loosely based on LX3JL's **ambed**, but is easily different enough to be considered an entirely original work. Here are some major differences with ambed: -- tcd uses both hardware-based and software-based vocoders, providing a bridge between the closed source vocoders used in DStar, DMR and YSF and open-source vocoders used in M17. -- **UNIX Sockets** are used to communicate between the reflector and this transcoder. This greatly simplifies the code and *significantly* improves transcoding performance. -- AMBE vocoders are dedicated to an assigned reflector channel. This prevents overloading when processing multiple voice streams and provides the best possible performance for the reflector's clients. +- tcd uses both hardware-based and software-based vocoders, providing a bridge between the closed source vocoders used in DStar, DMR NXDN and YSF and open-source vocoders used in M17 (Codec2) and P25 (IMBE). +- *UNIX Sockets* are used to communicate between the reflector and this transcoder. This greatly simplifies the code and significantly improves transcoding performance. +- Each configured module has a dedicated encoding and decoding instance running on a different thread. This prevents overloading when processing multiple voice streams and provides the best possible performance for the reflector's clients. ## Constraints and Requirements -This branch uses only one 300x device for the AMBE+(DStar) codec. The md380_vocoder library is used for the AMBE+2 (DMR/YSF/NXDN) codec. This means that this branch of tcd must run on an ARM platform like a RPi. - Currently, this program must be run locally with its paired URF reflector. Remote transcoding is not yet supported. -Only systemd-based operating systems are supported. Debian or Ubuntu is recommended. If you want to install this on a non-systemd based OS, you are on your own. Also, by default, tcd is built without gdb support. +Only systemd-based operating systems are supported. Debian or Ubuntu is recommended. If you want to install this on a non-systemd based OS, you are on your own. Also, by default, *tcd* is built without gdb support. + +The P25 IMBE software vocoder library is available [here](https://github.com/nostar/imbe_vocoder). See its README.md file for instructions for compiling and installating this library. + +If you are running tcd on an ARM-base processor, you can opt to use a software-based vocoder library available [here](https://github.com/nostar/md380_vocoder) for DMR/YSF vocoding. This library is used for the AMBE+2 (DMR/YSF/NXDN) codec. If you are going to use this library, *tcd* must run on an ARM platform like a RPi. Using this software solution means that you only need one DVSI device to handle D-Star vocoding. + +The DVSI devices need an FTDI driver which is available [here](https://ftdichip.com/drivers/d2xx-drivers). It's important to know that this driver will only work if the normal Linux kernel ftdi_sio and usbserial drivers are removed. This is automatically done by the system service file used for starting *tcd*. ## Download the repository In the parent directory of you urfd repository: ```bash -git clone https://github.com/n7tae/tcd.git +git clone https://github.com/nostar/tcd.git +cd tcd ``` To be perfectly clear, the urfd reflector repository clone and this clone **must be in the same directory**. -## Configuring, compiling, installing and other activities +## Compiling and configuring *tcd* -All other activities will be performed by the ./rconfig and ./radmin scripts in your urfd repo. + Copy the three configuration files to the working directory: -## 73 +```bash +cp config/* . +``` + +Use your favorite text editor to edit the following files: +- *tcd.mk* defines some compile time options. If you want to use the md380 vocoder, or change the installation directory, specify it here. Once you've set these options, do `make` to compile *tcd*. If you change `BINDIR`, you need to also change the `ExecStart` in your *tcd.service* file. +- *tcd.ini* defines run-time options. It is especially imporant that the `Transcoded` line for the tcd.ini file is exactly the same as the same line in the urfd.ini file! Suggested values for vocoder gains are provided. +- *tcd.service* is the systemd service file. You will need to modify the `ExecStart` line to successfully start *tcd* by specifying the path to your *tcd* executable and your tcd.ini file. + +## Installing *tcd* -DE N7TAE +It is easiest to install and uninstall *tcd* using the ./radmin scripts in your urfd repo. If you want to do this manually: + +```bash +sudo make install +sudo make uninstall +``` diff --git a/config/tcd.ini b/config/tcd.ini new file mode 100644 index 0000000..15f68a4 --- /dev/null +++ b/config/tcd.ini @@ -0,0 +1,18 @@ +# Transcoder Ini file +# +# this is a comment + +# VERY IMPORTANT: This need to be idential to the same line in the urfd ini file! +# This will either be a single module (for DVSI-3000), or +# up to three modules (for DVSI-3003). +Transcoded = A + +# All gain values are in dB. +# Gain values are limited to -24 to +24. Typical values will usually be less. +# Below are suggested values. +DStarGainIn = 16 +DStarGainOut = -16 +DmrYsfGainIn = -3 +DmrYsfGainOut = 0 +UsrpTxGain = 12 +UsrpRxGain = -6 diff --git a/config/tcd.mk b/config/tcd.mk new file mode 100644 index 0000000..8e5cfe8 --- /dev/null +++ b/config/tcd.mk @@ -0,0 +1,9 @@ +# This is where the executable will be installed `make install` +BINDIR = /usr/local/bin + +# set to true to build a transcoder that supports gdb +debug = false + +# set to true to use the md-380 software vocoder +# this will only work on an arm-based system, like a raspberry pi +swambe2 = false diff --git a/systemd/tcd.service b/config/tcd.service similarity index 83% rename from systemd/tcd.service rename to config/tcd.service index a029840..2481c4d 100644 --- a/systemd/tcd.service +++ b/config/tcd.service @@ -7,7 +7,7 @@ After=systemd-user-session.service network.target Type=simple ExecStartPre=-/sbin/rmmod ftdi_sio ExecStartPre=-/sbin/rmmod usbserial -ExecStart=/usr/local/bin/tcd +ExecStart=/usr/local/bin/tcd /PATH_TO_INI_FILE Restart=always [Install]