|
|
|
/*
|
|
|
|
Copyright (C) 2019-2021 Doug McLain
|
|
|
|
|
|
|
|
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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "mode.h"
|
|
|
|
#include <iostream>
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
#include <windows.h>
|
|
|
|
#else
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "m17.h"
|
|
|
|
#include "ysf.h"
|
|
|
|
#include "dmr.h"
|
|
|
|
#include "p25.h"
|
|
|
|
#include "nxdn.h"
|
|
|
|
#include "ref.h"
|
|
|
|
#include "xrf.h"
|
|
|
|
#include "dcs.h"
|
|
|
|
#include "iax.h"
|
|
|
|
|
|
|
|
#ifdef USE_FLITE
|
|
|
|
extern "C" {
|
|
|
|
extern cst_voice * register_cmu_us_slt(const char *);
|
|
|
|
extern cst_voice * register_cmu_us_kal16(const char *);
|
|
|
|
extern cst_voice * register_cmu_us_awb(const char *);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
Mode* Mode::create_mode(QString m)
|
|
|
|
{
|
|
|
|
Mode *mode = nullptr;
|
|
|
|
|
|
|
|
if(m == "M17"){
|
|
|
|
mode = new M17();
|
|
|
|
}
|
|
|
|
else if(m == "YSF" || m == "FCS"){
|
|
|
|
mode = new YSF();
|
|
|
|
}
|
|
|
|
else if(m == "DMR"){
|
|
|
|
mode = new DMR();
|
|
|
|
}
|
|
|
|
else if(m == "P25"){
|
|
|
|
mode = new P25();
|
|
|
|
}
|
|
|
|
else if(m == "NXDN"){
|
|
|
|
mode = new NXDN();
|
|
|
|
}
|
|
|
|
else if(m == "REF"){
|
|
|
|
mode = new REF();
|
|
|
|
}
|
|
|
|
else if(m == "XRF"){
|
|
|
|
mode = new XRF();
|
|
|
|
}
|
|
|
|
else if(m == "DCS"){
|
|
|
|
mode = new DCS();
|
|
|
|
}
|
|
|
|
else if(m == "IAX"){
|
|
|
|
mode = new IAX();
|
|
|
|
}
|
|
|
|
return mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
Mode::Mode()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Mode::~Mode()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::init(QString callsign, uint32_t dmrid, uint16_t nxdnid, char module, QString refname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout, bool mdirect)
|
|
|
|
{
|
|
|
|
m_dmrid = dmrid;
|
|
|
|
m_nxdnid = nxdnid;
|
|
|
|
m_module = module;
|
|
|
|
m_refname = refname;
|
|
|
|
m_ipv6 = ipv6;
|
|
|
|
m_vocoder = vocoder;
|
|
|
|
m_modemport = modem;
|
|
|
|
m_audioin = audioin;
|
|
|
|
m_audioout = audioout;
|
|
|
|
m_mdirect = mdirect;
|
|
|
|
|
|
|
|
m_modem = nullptr;
|
|
|
|
m_ambedev = nullptr;
|
|
|
|
m_hwrx = false;
|
|
|
|
m_hwtx = false;
|
|
|
|
m_tx = false;
|
|
|
|
m_ttsid = 0;
|
|
|
|
m_rxwatchdog = 0;
|
|
|
|
|
|
|
|
m_modeinfo.callsign = callsign;
|
|
|
|
m_modeinfo.gwid = 0;
|
|
|
|
m_modeinfo.srcid = dmrid;
|
|
|
|
m_modeinfo.dstid = 0;
|
|
|
|
m_modeinfo.host = host;
|
|
|
|
m_modeinfo.port = port;
|
|
|
|
m_modeinfo.count = 0;
|
|
|
|
m_modeinfo.frame_number = 0;
|
|
|
|
m_modeinfo.frame_total = 0;
|
|
|
|
m_modeinfo.streamid = 0;
|
|
|
|
m_modeinfo.stream_state = STREAM_IDLE;
|
|
|
|
m_modeinfo.sw_vocoder_loaded = false;
|
|
|
|
m_modeinfo.hw_vocoder_loaded = false;
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
m_rxtimerint = 19;
|
|
|
|
#else
|
|
|
|
m_rxtimerint = 20;
|
|
|
|
#endif
|
|
|
|
m_txtimerint = 19;
|
|
|
|
#ifdef USE_FLITE
|
|
|
|
flite_init();
|
|
|
|
voice_slt = register_cmu_us_slt(nullptr);
|
|
|
|
voice_kal = register_cmu_us_kal16(nullptr);
|
|
|
|
voice_awb = register_cmu_us_awb(nullptr);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::ambe_connect_status(bool s)
|
|
|
|
{
|
|
|
|
if(s){
|
|
|
|
#if !defined(Q_OS_IOS)
|
|
|
|
m_modeinfo.ambedesc = m_ambedev->get_ambe_description();
|
|
|
|
m_modeinfo.ambeprodid = m_ambedev->get_ambe_prodid();
|
|
|
|
m_modeinfo.ambeverstr = m_ambedev->get_ambe_verstring();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
m_modeinfo.ambeprodid = "Connect failed";
|
|
|
|
m_modeinfo.ambeverstr = "Connect failed";
|
|
|
|
}
|
|
|
|
emit update(m_modeinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::mmdvm_connect_status(bool s)
|
|
|
|
{
|
|
|
|
if(s){
|
|
|
|
//m_modeinfo.mmdvmdesc = m_modem->get_mmdvm_description();
|
|
|
|
#if !defined(Q_OS_IOS)
|
|
|
|
m_modeinfo.mmdvm = m_modem->get_mmdvm_version();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
m_modeinfo.mmdvm = "Connect failed";
|
|
|
|
}
|
|
|
|
emit update(m_modeinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::in_audio_vol_changed(qreal v)
|
|
|
|
{
|
|
|
|
m_audio->set_input_volume(v / m_attenuation);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::out_audio_vol_changed(qreal v)
|
|
|
|
{
|
|
|
|
m_audio->set_output_volume(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::agc_state_changed(int s)
|
|
|
|
{
|
|
|
|
m_audio->set_agc(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::begin_connect()
|
|
|
|
{
|
|
|
|
m_modeinfo.status = CONNECTING;
|
|
|
|
|
|
|
|
if((m_vocoder != "") && (m_mode != "M17")){
|
|
|
|
m_hwrx = true;
|
|
|
|
m_hwtx = true;
|
|
|
|
m_modeinfo.hw_vocoder_loaded = true;
|
|
|
|
#if !defined(Q_OS_IOS)
|
|
|
|
m_ambedev = new SerialAMBE(m_mode);
|
|
|
|
connect(m_ambedev, SIGNAL(connected(bool)), this, SLOT(ambe_connect_status(bool)));
|
|
|
|
connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe()));
|
|
|
|
connect(m_ambedev, SIGNAL(ambedev_ready()), this, SLOT(host_lookup()));
|
|
|
|
m_ambedev->connect_to_serial(m_vocoder);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
m_hwrx = false;
|
|
|
|
m_hwtx = false;
|
|
|
|
if(m_modemport == ""){
|
|
|
|
host_lookup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(m_modemport != ""){
|
|
|
|
#if !defined(Q_OS_IOS)
|
|
|
|
m_modem = new SerialModem(m_mode);
|
|
|
|
m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex);
|
|
|
|
m_modem->set_modem_params(m_baud, m_rxfreq, m_txfreq, m_txDelay, m_rxLevel, m_rfLevel, m_ysfTXHang, m_cwIdTXLevel, m_dstarTXLevel, m_dmrTXLevel, m_ysfTXLevel, m_p25TXLevel, m_nxdnTXLevel, m_pocsagTXLevel, m_m17TXLevel);
|
|
|
|
connect(m_modem, SIGNAL(connected(bool)), this, SLOT(mmdvm_connect_status(bool)));
|
|
|
|
connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray)));
|
|
|
|
connect(m_modem, SIGNAL(modem_ready()), this, SLOT(host_lookup()));
|
|
|
|
m_modem->connect_to_serial(m_modemport);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::host_lookup()
|
|
|
|
{
|
|
|
|
if(m_mdirect && (m_mode == "M17")){ // MMDVM_DIRECT currently only supported by M17
|
|
|
|
mmdvm_direct_connect();
|
|
|
|
}
|
|
|
|
else if(m_ipv6 && (m_modeinfo.host != "none")){
|
|
|
|
qDebug() << "Host == " << m_modeinfo.host;
|
|
|
|
QList<QHostAddress> h;
|
|
|
|
QHostInfo i;
|
|
|
|
h.append(QHostAddress(m_modeinfo.host));
|
|
|
|
i.setAddresses(h);
|
|
|
|
hostname_lookup(i);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
QHostInfo::lookupHost(m_modeinfo.host, this, SLOT(hostname_lookup(QHostInfo)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::toggle_tx(bool tx)
|
|
|
|
{
|
|
|
|
tx ? start_tx() : stop_tx();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::start_tx()
|
|
|
|
{
|
|
|
|
#if !defined(Q_OS_IOS)
|
|
|
|
if(m_hwtx){
|
|
|
|
m_ambedev->clear_queue();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
m_txcodecq.clear();
|
|
|
|
m_tx = true;
|
|
|
|
m_txcnt = 0;
|
|
|
|
m_ttscnt = 0;
|
|
|
|
m_rxtimer->stop();
|
|
|
|
m_modeinfo.streamid = 0;
|
|
|
|
m_modeinfo.stream_state = TRANSMITTING;
|
|
|
|
#ifdef USE_FLITE
|
|
|
|
|
|
|
|
if(m_ttsid == 1){
|
|
|
|
tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_kal);
|
|
|
|
}
|
|
|
|
else if(m_ttsid == 2){
|
|
|
|
tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_awb);
|
|
|
|
}
|
|
|
|
else if(m_ttsid == 3){
|
|
|
|
tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_slt);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if(!m_txtimer->isActive()){
|
|
|
|
if(m_ttsid == 0){
|
|
|
|
m_audio->set_input_buffer_size(640);
|
|
|
|
m_audio->start_capture();
|
|
|
|
//audioin->start(&audio_buffer);
|
|
|
|
}
|
|
|
|
m_txtimer->start(m_txtimerint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::stop_tx()
|
|
|
|
{
|
|
|
|
m_tx = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Mode::load_vocoder_plugin()
|
|
|
|
{
|
|
|
|
#ifdef VOCODER_PLUGIN
|
|
|
|
QString config_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
|
|
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WIN)
|
|
|
|
config_path += "/dudetronics";
|
|
|
|
#endif
|
|
|
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
|
|
QString voc = config_path + "/vocoder_plugin." + QSysInfo::productType() + "." + QSysInfo::currentCpuArchitecture();
|
|
|
|
#else
|
|
|
|
QStringList l = QSysInfo::buildAbi().split('-');
|
|
|
|
//QString voc = config_path + "/vocoder_plugin." + QSysInfo::kernelType() + "." + QSysInfo::currentCpuArchitecture();
|
|
|
|
QString voc = config_path + "/vocoder_plugin." + QSysInfo::kernelType() + "." + l.at(0);
|
|
|
|
#endif
|
|
|
|
#if !defined(Q_OS_WIN)
|
|
|
|
//QString voc = "/mnt/data/src/mbe_vocoder/vocoder_plugin.linux.x86_64.so";
|
|
|
|
void* a = dlopen(voc.toLocal8Bit(), RTLD_LAZY);
|
|
|
|
if (!a) {
|
|
|
|
qDebug() << "Cannot load library: " << QString::fromLocal8Bit(dlerror());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
dlerror();
|
|
|
|
|
|
|
|
create_t* create_a = (create_t*) dlsym(a, "create");
|
|
|
|
const char* dlsym_error = dlerror();
|
|
|
|
|
|
|
|
if (dlsym_error) {
|
|
|
|
qDebug() << "Cannot load symbol create: " << QString::fromLocal8Bit(dlsym_error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_mbevocoder = create_a();
|
|
|
|
qDebug() << voc + " loaded";
|
|
|
|
return true;
|
|
|
|
#else
|
|
|
|
HINSTANCE hinstvoclib;
|
|
|
|
hinstvoclib = LoadLibrary(reinterpret_cast<LPCWSTR>(voc.utf16()));
|
|
|
|
|
|
|
|
if (hinstvoclib != NULL) {
|
|
|
|
create_t* create_a = (create_t*)GetProcAddress(hinstvoclib, "create");
|
|
|
|
|
|
|
|
if (create_a != NULL) {
|
|
|
|
m_mbevocoder = create_a();
|
|
|
|
qDebug() << voc + " loaded";
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#else
|
|
|
|
#if defined(Q_OS_IOS)
|
|
|
|
QString config_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/dudetronics";
|
|
|
|
QString voc = config_path + "/vocoder_plugin." + QSysInfo::productType() + "." + QSysInfo::currentCpuArchitecture();
|
|
|
|
if(QFileInfo::exists(voc)){
|
|
|
|
m_mbevocoder = new VocoderPlugin();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
m_mbevocoder = new VocoderPlugin();
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mode::deleteLater()
|
|
|
|
{
|
|
|
|
if(m_modeinfo.status == CONNECTED_RW){
|
|
|
|
//m_udp->disconnect();
|
|
|
|
//m_ping_timer->stop();
|
|
|
|
send_disconnect();
|
|
|
|
delete m_audio;
|
|
|
|
#if !defined(Q_OS_IOS)
|
|
|
|
if(m_hwtx){
|
|
|
|
delete m_ambedev;
|
|
|
|
}
|
|
|
|
if(m_modem){
|
|
|
|
delete m_modem;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
m_modeinfo.count = 0;
|
|
|
|
QObject::deleteLater();
|
|
|
|
}
|