Merge pull request #1 from n7tae/main

run-time config, higher precision equalizer
main
nostar 3 years ago committed by GitHub
commit 55279d7ba4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

4
.gitignore vendored

@ -29,9 +29,5 @@
# Visual Studio # Visual Studio
.vscode .vscode
#files
configure.h
configure.mk
# Executables # Executables
tcd tcd

@ -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 <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#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<std::string> &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<std::string> 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;
}
}

@ -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 <cstdint>
#include <string>
#include <regex>
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;
};

@ -28,30 +28,23 @@
#include "TranscoderPacket.h" #include "TranscoderPacket.h"
#include "Controller.h" #include "Controller.h"
#include "Configure.h"
extern CConfigure g_Conf;
//#define AMBE_GAIN 16 //Encoder gain in dB (I use 16 here) int32_t CController::calcNumerator(int32_t db) const
//#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)
{ {
float ratio = powf(10.0, (db/20.0)); float num = 256.0f * powf(10.0f, (float(db)/20.0f));
if(db < 0){
ratio = (1/ratio) * (-1);
}
return (int16_t)roundf(ratio); return int32_t(roundf(num));
} }
CController::CController() : keep_running(true) {} CController::CController() : keep_running(true) {}
bool CController::Start() bool CController::Start()
{ {
usrp_rxgain = calcGainVal(USRP_RXGAIN); usrp_rx_num = calcNumerator(g_Conf.GetGain(EGainType::usrprx));
usrp_txgain = calcGainVal(USRP_TXGAIN); usrp_tx_num = calcNumerator(g_Conf.GetGain(EGainType::usrptx));
if (InitVocoders() || reader.Open(REF2TC)) if (InitVocoders() || reader.Open(REF2TC))
{ {
@ -130,7 +123,7 @@ bool CController::DiscoverFtdiDevices(std::list<std::pair<std::string, std::stri
bool CController::InitVocoders() bool CController::InitVocoders()
{ {
// M17 "devices", one for each module // M17 "devices", one for each module
const std::string modules(TRANSCODED_MODULES); const std::string modules(g_Conf.GetTCMods());
for ( auto c : modules) for ( auto c : modules)
{ {
c2_16[c] = std::unique_ptr<CCodec2>(new CCodec2(false)); c2_16[c] = std::unique_ptr<CCodec2>(new CCodec2(false));
@ -196,7 +189,8 @@ bool CController::InitVocoders()
dstar_device = std::unique_ptr<CDVDevice>(new CDV3000(Encoding::dstar)); dstar_device = std::unique_ptr<CDVDevice>(new CDV3000(Encoding::dstar));
#ifdef USE_SW_AMBE2 #ifdef USE_SW_AMBE2
md380_init(); 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 #else
dmrsf_device = std::unique_ptr<CDVDevice>(new CDV3000(Encoding::dmrsf)); dmrsf_device = std::unique_ptr<CDVDevice>(new CDV3000(Encoding::dmrsf));
#endif #endif
@ -213,7 +207,7 @@ bool CController::InitVocoders()
if (dstar_device) 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; return true;
deviceset.pop_front(); deviceset.pop_front();
} }
@ -225,7 +219,7 @@ bool CController::InitVocoders()
#ifndef USE_SW_AMBE2 #ifndef USE_SW_AMBE2
if (dmrsf_device) 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; return true;
deviceset.pop_front(); deviceset.pop_front();
} }
@ -415,22 +409,19 @@ void CController::ProcessC2Thread()
#ifdef USE_SW_AMBE2 #ifdef USE_SW_AMBE2
void CController::AudiotoSWAMBE2(std::shared_ptr<CTranscoderPacket> packet) void CController::AudiotoSWAMBE2(std::shared_ptr<CTranscoderPacket> packet)
{ {
const auto m = packet->GetModule();
uint8_t ambe2[9]; uint8_t ambe2[9];
int16_t tmp[160];
const int16_t *p = packet->GetAudioSamples(); const int16_t *p = packet->GetAudioSamples();
const uint32_t g = abs(ambe_gain);
for(int i = 0; i < 160; ++i){ if (ambe_in_num != 256)
if(ambe_gain < 0){ {
tmp[i] = p[i] / g; int16_t tmp[160];
} for(int i = 0; i < 160; ++i)
else{ tmp[i] = int16_t((p[i] * ambe_in_num) >> 8);
tmp[i] = p[i] * g; md380_encode_fec(ambe2, tmp);
}
} }
else
md380_encode_fec(ambe2, p);
md380_encode_fec(ambe2, tmp);
packet->SetDMRData(ambe2); packet->SetDMRData(ambe2);
// we might be all done... // we might be all done...
@ -441,9 +432,15 @@ void CController::AudiotoSWAMBE2(std::shared_ptr<CTranscoderPacket> packet)
void CController::SWAMBE2toAudio(std::shared_ptr<CTranscoderPacket> packet) void CController::SWAMBE2toAudio(std::shared_ptr<CTranscoderPacket> packet)
{ {
int16_t tmp[160] = {0}; int16_t tmp[160];
md380_decode_fec(packet->GetDMRData(), tmp); 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); packet->SetAudioSamples(tmp, false);
dstar_device->AddPacket(packet); dstar_device->AddPacket(packet);
codec2_queue.push(packet); codec2_queue.push(packet);
imbe_queue.push(packet); imbe_queue.push(packet);
@ -528,20 +525,18 @@ void CController::ProcessIMBEThread()
void CController::AudiotoUSRP(std::shared_ptr<CTranscoderPacket> packet) void CController::AudiotoUSRP(std::shared_ptr<CTranscoderPacket> packet)
{ {
int16_t tmp[160];
const int16_t *p = packet->GetAudioSamples(); const int16_t *p = packet->GetAudioSamples();
const uint32_t g = abs(usrp_txgain);
for(int i = 0; i < 160; ++i){ if (usrp_tx_num != 256)
if(usrp_txgain < 0){ {
tmp[i] = p[i] / g; int16_t tmp[160];
} for(int i = 0; i < 160; ++i)
else{ tmp[i] = int16_t((p[i] * usrp_tx_num) >> 8);
tmp[i] = p[i] * g; packet->SetUSRPData(tmp);
}
} }
else
packet->SetUSRPData(p);
packet->SetUSRPData(tmp);
// we might be all done... // we might be all done...
send_mux.lock(); send_mux.lock();
@ -551,21 +546,18 @@ void CController::AudiotoUSRP(std::shared_ptr<CTranscoderPacket> packet)
void CController::USRPtoAudio(std::shared_ptr<CTranscoderPacket> packet) void CController::USRPtoAudio(std::shared_ptr<CTranscoderPacket> packet)
{ {
int16_t tmp[160];
const int16_t *p = packet->GetUSRPData(); const int16_t *p = packet->GetUSRPData();
const uint32_t g = abs(usrp_rxgain);
for(int i = 0; i < 160; ++i){ if (usrp_rx_num != 256)
if(usrp_rxgain < 0){ {
tmp[i] = p[i] / g; int16_t tmp[160];
} for(int i = 0; i < 160; ++i)
else{ tmp[i] = int16_t((p[i] * usrp_rx_num) >> 8);
tmp[i] = p[i] * g; packet->SetAudioSamples(tmp, false);
}
} }
else
packet->SetAudioSamples(p, false);
//packet->SetAudioSamples(packet->GetUSRPData(), false);
packet->SetAudioSamples(tmp, false);
dstar_device->AddPacket(packet); dstar_device->AddPacket(packet);
codec2_queue.push(packet); codec2_queue.push(packet);

@ -30,7 +30,6 @@
#include "DV3000.h" #include "DV3000.h"
#include "DV3003.h" #include "DV3003.h"
#include "UnixDgramSocket.h" #include "UnixDgramSocket.h"
#include "configure.h"
class CController class CController
{ {
@ -59,11 +58,10 @@ protected:
CPacketQueue imbe_queue; CPacketQueue imbe_queue;
CPacketQueue usrp_queue; CPacketQueue usrp_queue;
std::mutex send_mux; std::mutex send_mux;
int16_t ambe_gain; int32_t ambe_in_num, ambe_out_num, usrp_rx_num, usrp_tx_num;
int16_t usrp_rxgain;
int16_t usrp_txgain;
imbe_vocoder p25vocoder; imbe_vocoder p25vocoder;
int32_t calcNumerator(int32_t db) const;
bool DiscoverFtdiDevices(std::list<std::pair<std::string, std::string>> &found); bool DiscoverFtdiDevices(std::list<std::pair<std::string, std::string>> &found);
bool InitVocoders(); bool InitVocoders();
// processing threads // processing threads

@ -33,10 +33,10 @@
#include <thread> #include <thread>
#include "DV3000.h" #include "DV3000.h"
#include "configure.h" #include "Configure.h"
#include "Controller.h" #include "Controller.h"
extern CController Controller; extern CController g_Cont;
CDV3000::CDV3000(Encoding t) : CDVDevice(t) {} 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? if (Encoding::dstar == type) // is this a DMR or a DStar device?
{ {
Controller.dstar_mux.lock(); g_Cont.dstar_mux.lock();
Controller.RouteDstPacket(packet); g_Cont.RouteDstPacket(packet);
Controller.dstar_mux.unlock(); g_Cont.dstar_mux.unlock();
} }
else else
{ {
Controller.dmrst_mux.lock(); g_Cont.dmrst_mux.lock();
Controller.RouteDmrPacket(packet); g_Cont.RouteDmrPacket(packet);
Controller.dmrst_mux.unlock(); g_Cont.dmrst_mux.unlock();
} }
} }
} }

@ -33,10 +33,10 @@
#include <thread> #include <thread>
#include "DV3003.h" #include "DV3003.h"
#include "configure.h" #include "Configure.h"
#include "Controller.h" #include "Controller.h"
extern CController Controller; extern CController g_Cont;
CDV3003::CDV3003(Encoding t) : CDVDevice(t) {} 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? if (Encoding::dstar == type) // is this a DMR or a DStar device?
{ {
Controller.dstar_mux.lock(); g_Cont.dstar_mux.lock();
Controller.RouteDstPacket(packet); g_Cont.RouteDstPacket(packet);
Controller.dstar_mux.unlock(); g_Cont.dstar_mux.unlock();
} }
else else
{ {
Controller.dmrst_mux.lock(); g_Cont.dmrst_mux.lock();
Controller.RouteDmrPacket(packet); g_Cont.RouteDmrPacket(packet);
Controller.dmrst_mux.unlock(); g_Cont.dmrst_mux.unlock();
} }
} }
} }

@ -34,10 +34,9 @@
#include <thread> #include <thread>
#include "DVSIDevice.h" #include "DVSIDevice.h"
#include "configure.h" #include "Configure.h"
#include "Controller.h"
extern CController Controller; extern CConfigure g_Conf;
CDVDevice::CDVDevice(Encoding t) : type(t), ftHandle(nullptr), buffer_depth(0), keep_running(true) 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() void CDVDevice::FeedDevice()
{ {
const std::string modules(TRANSCODED_MODULES); const std::string modules(g_Conf.GetTCMods());
const auto n = modules.size(); const auto n = modules.size();
while (keep_running) while (keep_running)
{ {

@ -18,20 +18,31 @@
#include <iostream> #include <iostream>
#include "Controller.h" #include "Controller.h"
#include "Configure.h"
// the global controller object // the global objects
CController Controller; 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;
}
if (g_Conf.ReadData(argv[1]))
return EXIT_FAILURE;
if (g_Cont.Start())
return EXIT_FAILURE; return EXIT_FAILURE;
std::cout << "Hybrid Transcoder version 0.0.5 successfully started" << std::endl; std::cout << "Hybrid Transcoder version 0.1.0 successfully started" << std::endl;
pause(); pause();
Controller.Stop(); g_Cont.Stop();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

@ -1,12 +1,6 @@
#Copyright (C) 2021 by Thomas A. Early, N7TAE #Copyright (C) 2021 by Thomas A. Early, N7TAE
include configure.mk include tcd.mk
# If you are going to change this path, you will
# need to update the systemd service script
BINDIR = /usr/local/bin
swambe2 = false
GCC = g++ GCC = g++
@ -49,7 +43,7 @@ clean :
# The install and uninstall targets need to be run by root # The install and uninstall targets need to be run by root
install : $(EXE) install : $(EXE)
cp $(EXE) $(BINDIR) cp $(EXE) $(BINDIR)
cp systemd/$(EXE).service /etc/systemd/system/ cp $(EXE).service /etc/systemd/system/
systemctl enable $(EXE) systemctl enable $(EXE)
systemctl daemon-reload systemctl daemon-reload
systemctl start $(EXE) systemctl start $(EXE)

@ -4,46 +4,57 @@ tcd is a hybrid digital voice transcoder for ham radio used by the new URF refle
## Introduction ## 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). This is the only transcoder that will work with the [URF reflector](https://github.com/nostar/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 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: 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. - 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. - *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. - 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 ## 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. 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 ## Download the repository
In the parent directory of you urfd repository: In the parent directory of you urfd repository:
```bash ```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**. 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
```

@ -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

@ -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

@ -7,7 +7,7 @@ After=systemd-user-session.service network.target
Type=simple Type=simple
ExecStartPre=-/sbin/rmmod ftdi_sio ExecStartPre=-/sbin/rmmod ftdi_sio
ExecStartPre=-/sbin/rmmod usbserial ExecStartPre=-/sbin/rmmod usbserial
ExecStart=/usr/local/bin/tcd ExecStart=/usr/local/bin/tcd /PATH_TO_INI_FILE
Restart=always Restart=always
[Install] [Install]
Loading…
Cancel
Save

Powered by TurnKey Linux.