run-time config

main
Tom Early 3 years ago
parent 3c4751ad0b
commit 8fa16713d1

4
.gitignore vendored

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

@ -0,0 +1,172 @@
/*
* 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 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
badParam(key);
break;
}
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 << " = " << int(dstar_in) << std::endl;
std::cout << DSTARGAINOUT << " = " << int(dstar_out) << std::endl;
std::cout << DMRGAININ << " = " << int(dmr_in) << std::endl;
std::cout << DMRGAINOUT << " = " << int(dmr_out) << 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 < -90 || i > 90 )
{
std::cout << "WARNING: " << key << " = " << value << " is out of range. Reset to zero!" << std::endl;
i = 0;
}
return i;
}
void CConfigure::badParam(const std::string &key) const
{
std::cout << "WARNING: Unexpected parameter: '" << key << "'" << std::endl;
}
int8_t CConfigure::GetGain(EGainType gt) const
{
switch (gt)
{
case EGainType::dmrin: return int8_t(dmr_in);
case EGainType::dmrout: return int8_t(dmr_out);
case EGainType::dstarin: return int8_t(dstar_in);
case EGainType::dstarout: return int8_t(dstar_out);
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 };
#define IS_TRUE(a) ((a)=='t' || (a)=='T' || (a)=='1')
class CConfigure
{
public:
bool ReadData(const std::string &path);
int8_t 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;
int getSigned(const std::string &key, const std::string &value) const;
void badParam(const std::string &key) const;
};

@ -22,13 +22,16 @@
#include <sstream>
#include <fstream>
#include <thread>
#ifdef USE_SW_AMBE2
#include <md380_vocoder.h>
#endif
#include "Configure.h"
#include "TranscoderPacket.h"
#include "Controller.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)
@ -38,11 +41,11 @@
int16_t calcGainVal(float db)
{
float ratio = powf(10.0, (db/20.0));
if(db < 0){
ratio = (1/ratio) * (-1);
}
return (int16_t)roundf(ratio);
}
@ -52,7 +55,7 @@ bool CController::Start()
{
usrp_rxgain = calcGainVal(USRP_RXGAIN);
usrp_txgain = calcGainVal(USRP_TXGAIN);
if (InitVocoders() || reader.Open(REF2TC))
{
keep_running = false;
@ -130,7 +133,7 @@ bool CController::DiscoverFtdiDevices(std::list<std::pair<std::string, std::stri
bool CController::InitVocoders()
{
// M17 "devices", one for each module
const std::string modules(TRANSCODED_MODULES);
const std::string modules(g_Conf.GetTCMods());
for ( auto c : modules)
{
c2_16[c] = std::unique_ptr<CCodec2>(new CCodec2(false));
@ -210,10 +213,10 @@ bool CController::InitVocoders()
dmrsf_device = std::unique_ptr<CDVDevice>(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, g_Conf.GetGain(EGainType::dstarin), g_Conf.GetGain(EGainType::dstarout)))
return true;
deviceset.pop_front();
}
@ -225,7 +228,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, g_Conf.GetGain(EGainType::dmrin), g_Conf.GetGain(EGainType::dmrout)))
return true;
deviceset.pop_front();
}
@ -239,7 +242,7 @@ bool CController::InitVocoders()
// and start them (or it) up!
dstar_device->Start();
#ifndef USE_SW_AMBE2
dmrsf_device->Start();
#endif
@ -331,7 +334,7 @@ void CController::Codec2toAudio(std::shared_ptr<CTranscoderPacket> packet)
{
uint8_t ambe2[9];
uint8_t imbe[11];
if (packet->IsSecond())
{
if (packet->GetCodecIn() == ECodecType::c2_1600)
@ -380,7 +383,7 @@ void CController::Codec2toAudio(std::shared_ptr<CTranscoderPacket> packet)
#else
dmrsf_device->AddPacket(packet);
#endif
packet->SetDMRData(ambe2);
p25vocoder.encode_4400((int16_t*)packet->GetAudioSamples(), imbe);
packet->SetP25Data(imbe);
@ -421,7 +424,7 @@ void CController::AudiotoSWAMBE2(std::shared_ptr<CTranscoderPacket> packet)
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;
@ -430,10 +433,10 @@ void CController::AudiotoSWAMBE2(std::shared_ptr<CTranscoderPacket> packet)
tmp[i] = p[i] * g;
}
}
md380_encode_fec(ambe2, tmp);
packet->SetDMRData(ambe2);
// we might be all done...
send_mux.lock();
if (packet->AllCodecsAreSet() && packet->HasNotBeenSent()) SendToReflector(packet);
@ -494,13 +497,13 @@ void CController::IMBEtoAudio(std::shared_ptr<CTranscoderPacket> packet)
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);
}
@ -532,7 +535,7 @@ void CController::AudiotoUSRP(std::shared_ptr<CTranscoderPacket> 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;
@ -541,9 +544,9 @@ void CController::AudiotoUSRP(std::shared_ptr<CTranscoderPacket> packet)
tmp[i] = p[i] * g;
}
}
packet->SetUSRPData(tmp);
// we might be all done...
send_mux.lock();
if (packet->AllCodecsAreSet() && packet->HasNotBeenSent()) SendToReflector(packet);
@ -555,7 +558,7 @@ void CController::USRPtoAudio(std::shared_ptr<CTranscoderPacket> 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;
@ -564,18 +567,18 @@ void CController::USRPtoAudio(std::shared_ptr<CTranscoderPacket> packet)
tmp[i] = p[i] * g;
}
}
//packet->SetAudioSamples(packet->GetUSRPData(), false);
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
imbe_queue.push(packet);
}

@ -30,7 +30,6 @@
#include "DV3000.h"
#include "DV3003.h"
#include "UnixDgramSocket.h"
#include "configure.h"
class CController
{
@ -55,7 +54,7 @@ protected:
std::unique_ptr<CDVDevice> dstar_device, dmrsf_device;
CPacketQueue codec2_queue;
CPacketQueue imbe_queue;
CPacketQueue usrp_queue;
std::mutex send_mux;
@ -69,7 +68,7 @@ protected:
// processing threads
void ReadReflectorThread();
void ProcessC2Thread();
void ProcessIMBEThread();
void ProcessUSRPThread();
void Codec2toAudio(std::shared_ptr<CTranscoderPacket> packet);

@ -18,20 +18,31 @@
#include <iostream>
#include "Controller.h"
#include "Configure.h"
// the global controller object
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;
}
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;
}

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

@ -36,13 +36,22 @@ In the parent directory of you urfd repository:
```bash
git clone https://github.com/n7tae/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
## Configuring and compiling
All other activities will be performed by the ./rconfig and ./radmin scripts in your urfd repo.
Copy the three configuration files: `cp config/* .`
Use your favorite text editor:
- *tcd.mk* defines some compile time options. Once you've set these options, do `make`
- *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!
- *tcd.service* is the systemd service file. be sure the `ExecStart` line will successfully start tcd!
## Installing and other activities
If the transcoder is local, all other activities will be performed by the ./radmin scripts in your urfd repo.
## 73

@ -0,0 +1,15 @@
# 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 letter (for DVSI-3000), or
# three letters (for DVSI-3003)
Transcoded = A
# Gain (or attenuation) in dB should be integers in the range of +/-90
# Something is probably terribly wrong if you need more than +/-20
DStarGainIn = 0
DStarGainOut = 0
DmrYsfGainIn = 0
DmrYsfGainOut = 0

@ -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
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]
Loading…
Cancel
Save

Powered by TurnKey Linux.