Initial commit

pull/2/head
Doug McLain 3 years ago
commit f5447aee1c

@ -0,0 +1,56 @@
/*
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/>.
*/
import QtQuick 2.10
Item {
id: aboutTab
Rectangle{
id: helpText
x: 20
y: 20
width: parent.width - 40
height: parent.height - 40
color: "#252424"
Flickable{
anchors.fill: parent
contentWidth: parent.width
contentHeight: aboutText.y + aboutText.height
flickableDirection: Flickable.VerticalFlick
clip: true
Text {
id: aboutText
width: helpText.width
wrapMode: Text.WordWrap
color: "white"
text: qsTr( "\nDROID-Star git build " + droidstar.get_software_build() +
"\nPlatform:\t" + droidstar.get_platform() +
"\nArchitecture:\t" + droidstar.get_arch() +
"\nBuild ABI:\t" + droidstar.get_build_abi() +
"\n\nCopyright (C) 2019-2021 Doug McLain AD8DP\n" +
"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; version 2.\n\n" +
"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.\n\n" +
"You should have received a copy of the GNU General Public License along with this " +
"program. If not, see <http://www.gnu.org/licenses/>")
}
}
}
}

@ -0,0 +1,269 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
*
* 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 "CRCenc.h"
//#include "Utils.h"
//#include "Log.h"
#include <cstdint>
#include <cstdio>
#include <cassert>
#include <cmath>
const uint8_t CRC8_TABLE[] = {
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31,
0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9,
0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1,
0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE,
0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16,
0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80,
0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8,
0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10,
0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F,
0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7,
0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF,
0xFA, 0xFD, 0xF4, 0xF3, 0x01 };
const uint16_t CCITT16_TABLE1[] = {
0x0000U, 0x1189U, 0x2312U, 0x329bU, 0x4624U, 0x57adU, 0x6536U, 0x74bfU,
0x8c48U, 0x9dc1U, 0xaf5aU, 0xbed3U, 0xca6cU, 0xdbe5U, 0xe97eU, 0xf8f7U,
0x1081U, 0x0108U, 0x3393U, 0x221aU, 0x56a5U, 0x472cU, 0x75b7U, 0x643eU,
0x9cc9U, 0x8d40U, 0xbfdbU, 0xae52U, 0xdaedU, 0xcb64U, 0xf9ffU, 0xe876U,
0x2102U, 0x308bU, 0x0210U, 0x1399U, 0x6726U, 0x76afU, 0x4434U, 0x55bdU,
0xad4aU, 0xbcc3U, 0x8e58U, 0x9fd1U, 0xeb6eU, 0xfae7U, 0xc87cU, 0xd9f5U,
0x3183U, 0x200aU, 0x1291U, 0x0318U, 0x77a7U, 0x662eU, 0x54b5U, 0x453cU,
0xbdcbU, 0xac42U, 0x9ed9U, 0x8f50U, 0xfbefU, 0xea66U, 0xd8fdU, 0xc974U,
0x4204U, 0x538dU, 0x6116U, 0x709fU, 0x0420U, 0x15a9U, 0x2732U, 0x36bbU,
0xce4cU, 0xdfc5U, 0xed5eU, 0xfcd7U, 0x8868U, 0x99e1U, 0xab7aU, 0xbaf3U,
0x5285U, 0x430cU, 0x7197U, 0x601eU, 0x14a1U, 0x0528U, 0x37b3U, 0x263aU,
0xdecdU, 0xcf44U, 0xfddfU, 0xec56U, 0x98e9U, 0x8960U, 0xbbfbU, 0xaa72U,
0x6306U, 0x728fU, 0x4014U, 0x519dU, 0x2522U, 0x34abU, 0x0630U, 0x17b9U,
0xef4eU, 0xfec7U, 0xcc5cU, 0xddd5U, 0xa96aU, 0xb8e3U, 0x8a78U, 0x9bf1U,
0x7387U, 0x620eU, 0x5095U, 0x411cU, 0x35a3U, 0x242aU, 0x16b1U, 0x0738U,
0xffcfU, 0xee46U, 0xdcddU, 0xcd54U, 0xb9ebU, 0xa862U, 0x9af9U, 0x8b70U,
0x8408U, 0x9581U, 0xa71aU, 0xb693U, 0xc22cU, 0xd3a5U, 0xe13eU, 0xf0b7U,
0x0840U, 0x19c9U, 0x2b52U, 0x3adbU, 0x4e64U, 0x5fedU, 0x6d76U, 0x7cffU,
0x9489U, 0x8500U, 0xb79bU, 0xa612U, 0xd2adU, 0xc324U, 0xf1bfU, 0xe036U,
0x18c1U, 0x0948U, 0x3bd3U, 0x2a5aU, 0x5ee5U, 0x4f6cU, 0x7df7U, 0x6c7eU,
0xa50aU, 0xb483U, 0x8618U, 0x9791U, 0xe32eU, 0xf2a7U, 0xc03cU, 0xd1b5U,
0x2942U, 0x38cbU, 0x0a50U, 0x1bd9U, 0x6f66U, 0x7eefU, 0x4c74U, 0x5dfdU,
0xb58bU, 0xa402U, 0x9699U, 0x8710U, 0xf3afU, 0xe226U, 0xd0bdU, 0xc134U,
0x39c3U, 0x284aU, 0x1ad1U, 0x0b58U, 0x7fe7U, 0x6e6eU, 0x5cf5U, 0x4d7cU,
0xc60cU, 0xd785U, 0xe51eU, 0xf497U, 0x8028U, 0x91a1U, 0xa33aU, 0xb2b3U,
0x4a44U, 0x5bcdU, 0x6956U, 0x78dfU, 0x0c60U, 0x1de9U, 0x2f72U, 0x3efbU,
0xd68dU, 0xc704U, 0xf59fU, 0xe416U, 0x90a9U, 0x8120U, 0xb3bbU, 0xa232U,
0x5ac5U, 0x4b4cU, 0x79d7U, 0x685eU, 0x1ce1U, 0x0d68U, 0x3ff3U, 0x2e7aU,
0xe70eU, 0xf687U, 0xc41cU, 0xd595U, 0xa12aU, 0xb0a3U, 0x8238U, 0x93b1U,
0x6b46U, 0x7acfU, 0x4854U, 0x59ddU, 0x2d62U, 0x3cebU, 0x0e70U, 0x1ff9U,
0xf78fU, 0xe606U, 0xd49dU, 0xc514U, 0xb1abU, 0xa022U, 0x92b9U, 0x8330U,
0x7bc7U, 0x6a4eU, 0x58d5U, 0x495cU, 0x3de3U, 0x2c6aU, 0x1ef1U, 0x0f78U };
const uint16_t CCITT16_TABLE2[] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 };
/*
bool CCRC::checkFiveBit(bool* in, unsigned int tcrc)
{
assert(in != NULL);
unsigned int crc;
//encodeFiveBit(in, crc);
return crc == tcrc;
}
*/
void CCRC::bitsToByteBE(const bool* bits, unsigned char& byte)
{
assert(bits != NULL);
byte = bits[0U] ? 0x80U : 0x00U;
byte |= bits[1U] ? 0x40U : 0x00U;
byte |= bits[2U] ? 0x20U : 0x00U;
byte |= bits[3U] ? 0x10U : 0x00U;
byte |= bits[4U] ? 0x08U : 0x00U;
byte |= bits[5U] ? 0x04U : 0x00U;
byte |= bits[6U] ? 0x02U : 0x00U;
byte |= bits[7U] ? 0x01U : 0x00U;
}
void CCRC::encodeFiveBit(const bool* in, unsigned int& tcrc)
{
assert(in != NULL);
unsigned short total = 0U;
for (unsigned int i = 0U; i < 72U; i += 8U) {
unsigned char c;
bitsToByteBE(in + i, c);
total += c;
}
total %= 31U;
tcrc = total;
}
void CCRC::addCCITT162(unsigned char *in, unsigned int length)
{
assert(in != NULL);
assert(length > 2U);
union {
uint16_t crc16;
uint8_t crc8[2U];
};
crc16 = 0U;
for (unsigned i = 0U; i < (length - 2U); i++)
crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]];
crc16 = ~crc16;
in[length - 1U] = crc8[0U];
in[length - 2U] = crc8[1U];
}
bool CCRC::checkCCITT162(const unsigned char *in, unsigned int length)
{
assert(in != NULL);
assert(length > 2U);
union {
uint16_t crc16;
uint8_t crc8[2U];
};
crc16 = 0U;
for (unsigned i = 0U; i < (length - 2U); i++)
crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]];
crc16 = ~crc16;
return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U];
}
void CCRC::addCCITT161(unsigned char *in, unsigned int length)
{
assert(in != NULL);
assert(length > 2U);
union {
uint16_t crc16;
uint8_t crc8[2U];
};
crc16 = 0xFFFFU;
for (unsigned int i = 0U; i < (length - 2U); i++)
crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]];
crc16 = ~crc16;
in[length - 2U] = crc8[0U];
in[length - 1U] = crc8[1U];
}
bool CCRC::checkCCITT161(const unsigned char *in, unsigned int length)
{
assert(in != NULL);
assert(length > 2U);
union {
uint16_t crc16;
uint8_t crc8[2U];
};
crc16 = 0xFFFFU;
for (unsigned int i = 0U; i < (length - 2U); i++)
crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]];
crc16 = ~crc16;
return crc8[0U] == in[length - 2U] && crc8[1U] == in[length - 1U];
}
unsigned char CCRC::crc8(const unsigned char *in, unsigned int length)
{
assert(in != NULL);
uint8_t crc = 0U;
for (unsigned int i = 0U; i < length; i++)
crc = CRC8_TABLE[crc ^ in[i]];
return crc;
}
unsigned char CCRC::addCRC(const unsigned char* in, unsigned int length)
{
assert(in != NULL);
unsigned char crc = 0U;
for (unsigned int i = 0U; i < length; i++)
crc += in[i];
return crc;
}

@ -0,0 +1,41 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
*
* 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.
*/
#if !defined(CRC_H)
#define CRC_H
class CCRC
{
public:
//static bool checkFiveBit(bool* in, unsigned int tcrc);
static void bitsToByteBE(const bool* bits, unsigned char& byte);
static void encodeFiveBit(const bool* in, unsigned int& tcrc);
static void addCCITT161(unsigned char* in, unsigned int length);
static void addCCITT162(unsigned char* in, unsigned int length);
static bool checkCCITT161(const unsigned char* in, unsigned int length);
static bool checkCCITT162(const unsigned char* in, unsigned int length);
static unsigned char crc8(const unsigned char* in, unsigned int length);
static unsigned char addCRC(const unsigned char* in, unsigned int length);
};
#endif

@ -0,0 +1,209 @@
/*
* Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX
*
* 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; version 2 of the License.
*
* 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.
*/
#include "DMRData.h"
#include "DMRDefines.h"
#include <cstdio>
#include <cstring>
#include <cassert>
CDMRData::CDMRData(const CDMRData& data) :
m_slotNo(data.m_slotNo),
m_data(NULL),
m_srcId(data.m_srcId),
m_dstId(data.m_dstId),
m_flco(data.m_flco),
m_dataType(data.m_dataType),
m_seqNo(data.m_seqNo),
m_missing(data.m_missing),
m_n(data.m_n),
m_ber(data.m_ber),
m_rssi(data.m_rssi),
m_streamId(data.m_streamId)
{
m_data = new unsigned char[2U * DMR_FRAME_LENGTH_BYTES];
::memcpy(m_data, data.m_data, 2U * DMR_FRAME_LENGTH_BYTES);
}
CDMRData::CDMRData() :
m_slotNo(1U),
m_data(NULL),
m_srcId(0U),
m_dstId(0U),
m_flco(FLCO_GROUP),
m_dataType(0U),
m_seqNo(0U),
m_missing(false),
m_n(0U),
m_ber(0U),
m_rssi(0U),
m_streamId(0U)
{
m_data = new unsigned char[2U * DMR_FRAME_LENGTH_BYTES];
}
CDMRData::~CDMRData()
{
delete[] m_data;
}
CDMRData& CDMRData::operator=(const CDMRData& data)
{
if (this != &data) {
::memcpy(m_data, data.m_data, DMR_FRAME_LENGTH_BYTES);
m_slotNo = data.m_slotNo;
m_srcId = data.m_srcId;
m_dstId = data.m_dstId;
m_flco = data.m_flco;
m_dataType = data.m_dataType;
m_seqNo = data.m_seqNo;
m_missing = data.m_missing;
m_n = data.m_n;
m_ber = data.m_ber;
m_rssi = data.m_rssi;
m_streamId = data.m_streamId;
}
return *this;
}
unsigned int CDMRData::getSlotNo() const
{
return m_slotNo;
}
void CDMRData::setSlotNo(unsigned int slotNo)
{
assert(slotNo == 1U || slotNo == 2U);
m_slotNo = slotNo;
}
unsigned char CDMRData::getDataType() const
{
return m_dataType;
}
void CDMRData::setDataType(unsigned char dataType)
{
m_dataType = dataType;
}
unsigned int CDMRData::getSrcId() const
{
return m_srcId;
}
void CDMRData::setSrcId(unsigned int id)
{
m_srcId = id;
}
unsigned int CDMRData::getDstId() const
{
return m_dstId;
}
void CDMRData::setDstId(unsigned int id)
{
m_dstId = id;
}
FLCO CDMRData::getFLCO() const
{
return m_flco;
}
void CDMRData::setFLCO(FLCO flco)
{
m_flco = flco;
}
unsigned char CDMRData::getSeqNo() const
{
return m_seqNo;
}
void CDMRData::setSeqNo(unsigned char seqNo)
{
m_seqNo = seqNo;
}
bool CDMRData::isMissing() const
{
return m_missing;
}
void CDMRData::setMissing(bool missing)
{
m_missing = missing;
}
unsigned char CDMRData::getN() const
{
return m_n;
}
void CDMRData::setN(unsigned char n)
{
m_n = n;
}
unsigned char CDMRData::getBER() const
{
return m_ber;
}
void CDMRData::setBER(unsigned char ber)
{
m_ber = ber;
}
unsigned char CDMRData::getRSSI() const
{
return m_rssi;
}
void CDMRData::setRSSI(unsigned char rssi)
{
m_rssi = rssi;
}
unsigned int CDMRData::getData(unsigned char* buffer) const
{
assert(buffer != NULL);
::memcpy(buffer, m_data, DMR_FRAME_LENGTH_BYTES);
return DMR_FRAME_LENGTH_BYTES;
}
void CDMRData::setData(const unsigned char* buffer)
{
assert(buffer != NULL);
::memcpy(m_data, buffer, DMR_FRAME_LENGTH_BYTES);
}
unsigned int CDMRData::getStreamId() const
{
return m_streamId;
}
void CDMRData::setStreamId(unsigned int id)
{
m_streamId = id;
}

@ -0,0 +1,78 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor, G4KLX
*
* 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; version 2 of the License.
*
* 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.
*/
#ifndef DMRData_H
#define DMRData_H
#include "DMRDefines.h"
class CDMRData {
public:
CDMRData(const CDMRData& data);
CDMRData();
~CDMRData();
CDMRData& operator=(const CDMRData& data);
unsigned int getSlotNo() const;
void setSlotNo(unsigned int slotNo);
unsigned int getSrcId() const;
void setSrcId(unsigned int id);
unsigned int getDstId() const;
void setDstId(unsigned int id);
FLCO getFLCO() const;
void setFLCO(FLCO flco);
unsigned char getN() const;
void setN(unsigned char n);
unsigned char getSeqNo() const;
void setSeqNo(unsigned char seqNo);
unsigned char getDataType() const;
void setDataType(unsigned char dataType);
bool isMissing() const;
void setMissing(bool missing);
unsigned char getBER() const;
void setBER(unsigned char ber);
unsigned char getRSSI() const;
void setRSSI(unsigned char rssi);
void setData(const unsigned char* buffer);
unsigned int getData(unsigned char* buffer) const;
void setStreamId(unsigned int id);
unsigned int getStreamId() const;
private:
unsigned int m_slotNo;
unsigned char* m_data;
unsigned int m_srcId;
unsigned int m_dstId;
FLCO m_flco;
unsigned char m_dataType;
unsigned char m_seqNo;
bool m_missing;
unsigned char m_n;
unsigned char m_ber;
unsigned char m_rssi;
unsigned int m_streamId;
};
#endif

@ -0,0 +1,126 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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.
*/
#if !defined(DMRDefines_H)
#define DMRDefines_H
const unsigned char TAG_HEADER = 0x00U;
const unsigned char TAG_DATA = 0x01U;
const unsigned char TAG_LOST = 0x02U;
const unsigned char TAG_EOT = 0x03U;
const unsigned char TAG_NODATA = 0x04U;
const unsigned int DMR_FRAME_LENGTH_BITS = 264U;
const unsigned int DMR_FRAME_LENGTH_BYTES = 33U;
const unsigned int DMR_SYNC_LENGTH_BITS = 48U;
const unsigned int DMR_SYNC_LENGTH_BYTES = 6U;
const unsigned int DMR_EMB_LENGTH_BITS = 8U;
const unsigned int DMR_EMB_LENGTH_BYTES = 1U;
const unsigned int DMR_SLOT_TYPE_LENGTH_BITS = 8U;
const unsigned int DMR_SLOT_TYPE_LENGTH_BYTES = 1U;
const unsigned int DMR_EMBEDDED_SIGNALLING_LENGTH_BITS = 32U;
const unsigned int DMR_EMBEDDED_SIGNALLING_LENGTH_BYTES = 4U;
const unsigned int DMR_AMBE_LENGTH_BITS = 108U * 2U;
const unsigned int DMR_AMBE_LENGTH_BYTES = 27U;
const unsigned char BS_SOURCED_AUDIO_SYNC[] = {0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U};
const unsigned char BS_SOURCED_DATA_SYNC[] = {0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U};
const unsigned char MS_SOURCED_AUDIO_SYNC[] = {0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U};
const unsigned char MS_SOURCED_DATA_SYNC[] = {0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x70U};
const unsigned char DIRECT_SLOT1_AUDIO_SYNC[] = {0x05U, 0xD5U, 0x77U, 0xF7U, 0x75U, 0x7FU, 0xF0U};
const unsigned char DIRECT_SLOT1_DATA_SYNC[] = {0x0FU, 0x7FU, 0xDDU, 0x5DU, 0xDFU, 0xD5U, 0x50U};
const unsigned char DIRECT_SLOT2_AUDIO_SYNC[] = {0x07U, 0xDFU, 0xFDU, 0x5FU, 0x55U, 0xD5U, 0xF0U};
const unsigned char DIRECT_SLOT2_DATA_SYNC[] = {0x0DU, 0x75U, 0x57U, 0xF5U, 0xFFU, 0x7FU, 0x50U};
const unsigned char SYNC_MASK[] = {0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U};
// The PR FILL and Data Sync pattern.
const unsigned char DMR_IDLE_DATA[] = {TAG_DATA, 0x00U,
0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U,
0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U,
0xE4U, 0x65U, 0x17U, 0x1BU, 0x48U, 0xCAU, 0x6DU, 0x4FU, 0xC6U, 0x10U, 0xB4U};
// A silence frame only
const unsigned char DMR_SILENCE_DATA[] = {0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU, 0xB9U, 0xE8U,
0x81U, 0x52U, 0x60U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x73U, 0x00U,
0x2AU, 0x6BU, 0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU};
const unsigned char PAYLOAD_LEFT_MASK[] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U};
const unsigned char PAYLOAD_RIGHT_MASK[] = {0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU};
const unsigned char VOICE_LC_HEADER_CRC_MASK[] = {0x96U, 0x96U, 0x96U};
const unsigned char TERMINATOR_WITH_LC_CRC_MASK[] = {0x99U, 0x99U, 0x99U};
const unsigned char PI_HEADER_CRC_MASK[] = {0x69U, 0x69U};
const unsigned char DATA_HEADER_CRC_MASK[] = {0xCCU, 0xCCU};
const unsigned char CSBK_CRC_MASK[] = {0xA5U, 0xA5U};
const unsigned int DMR_SLOT_TIME = 60U;
const unsigned int AMBE_PER_SLOT = 3U;
const unsigned char DT_MASK = 0x0FU;
const unsigned char DT_VOICE_PI_HEADER = 0x00U;
const unsigned char DT_VOICE_LC_HEADER = 0x01U;
const unsigned char DT_TERMINATOR_WITH_LC = 0x02U;
const unsigned char DT_CSBK = 0x03U;
const unsigned char DT_DATA_HEADER = 0x06U;
const unsigned char DT_RATE_12_DATA = 0x07U;
const unsigned char DT_RATE_34_DATA = 0x08U;
const unsigned char DT_IDLE = 0x09U;
const unsigned char DT_RATE_1_DATA = 0x0AU;
// Dummy values
const unsigned char DT_VOICE_SYNC = 0xF0U;
const unsigned char DT_VOICE = 0xF1U;
const unsigned char DMR_IDLE_RX = 0x80U;
const unsigned char DMR_SYNC_DATA = 0x40U;
const unsigned char DMR_SYNC_AUDIO = 0x20U;
const unsigned char DMR_SLOT1 = 0x00U;
const unsigned char DMR_SLOT2 = 0x80U;
const unsigned char DPF_UDT = 0x00U;
const unsigned char DPF_RESPONSE = 0x01U;
const unsigned char DPF_UNCONFIRMED_DATA = 0x02U;
const unsigned char DPF_CONFIRMED_DATA = 0x03U;
const unsigned char DPF_DEFINED_SHORT = 0x0DU;
const unsigned char DPF_DEFINED_RAW = 0x0EU;
const unsigned char DPF_PROPRIETARY = 0x0FU;
const unsigned char FID_ETSI = 0U;
const unsigned char FID_DMRA = 16U;
enum FLCO {
FLCO_GROUP = 0,
FLCO_USER_USER = 3,
FLCO_TALKER_ALIAS_HEADER = 4,
FLCO_TALKER_ALIAS_BLOCK1 = 5,
FLCO_TALKER_ALIAS_BLOCK2 = 6,
FLCO_TALKER_ALIAS_BLOCK3 = 7,
FLCO_GPS_INFO = 8
};
#endif

@ -0,0 +1,138 @@
QT += quick quickcontrols2 network multimedia
android:QT += androidextras
linux:QT += serialport
CONFIG += c++11
LFLAGS +=
android:INCLUDEPATH += $$(HOME)/Android/android-build/include
LIBS += -limbe_vocoder -ldl
macx:QT += serialport
macx::INCLUDEPATH += /usr/local/include
macx:LIBS += -L/usr/local/lib -framework AVFoundation
macx:QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
macx:QMAKE_INFO_PLIST = Info.plist
ios:LIBS += -framework AVFoundation
ios:QMAKE_INFO_PLIST = Info.plist
VERSION_BUILD='$(shell cd $$PWD;git rev-parse --short HEAD)'
DEFINES += VERSION_NUMBER=\"\\\"$${VERSION_BUILD}\\\"\"
DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
#DEFINES += USE_FLITE
DEFINES += AMBESW_SUPPORTED
SOURCES += \
CRCenc.cpp \
DMRData.cpp \
Golay24128.cpp \
SHA256.cpp \
YSFConvolution.cpp \
YSFFICH.cpp \
androidserialport.cpp \
audioengine.cpp \
cbptc19696.cpp \
cgolay2087.cpp \
chamming.cpp \
codec.cpp \
codec2/codebooks.cpp \
codec2/codec2.cpp \
codec2/kiss_fft.cpp \
codec2/lpc.cpp \
codec2/nlp.cpp \
codec2/pack.cpp \
codec2/qbase.cpp \
codec2/quantise.cpp \
crs129.cpp \
dcscodec.cpp \
dmrcodec.cpp \
droidstar.cpp \
httpmanager.cpp \
iaxcodec.cpp \
m17codec.cpp \
main.cpp \
nxdncodec.cpp \
p25codec.cpp \
refcodec.cpp \
serialambe.cpp \
serialmodem.cpp \
xrfcodec.cpp \
ysfcodec.cpp
macx:OBJECTIVE_SOURCES += micpermission.mm
ios:OBJECTIVE_SOURCES += micpermission.mm
RESOURCES += qml.qrc
QML_IMPORT_PATH =
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
DISTFILES += \
android/AndroidManifest.xml \
android/build.gradle \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml \
images/log.png
contains(ANDROID_TARGET_ARCH,armeabi-v7a) {
LIBS += -L$$(HOME)/Android/local/lib
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
OTHER_FILES += android/src
}
contains(ANDROID_TARGET_ARCH,arm64-v8a) {
LIBS += -L$$(HOME)/Android/local/lib64
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
OTHER_FILES += android/src
}
ANDROID_ABIS = armeabi-v7a arm64-v8a
HEADERS += \
CRCenc.h \
DMRData.h \
DMRDefines.h \
Golay24128.h \
SHA256.h \
YSFConvolution.h \
YSFFICH.h \
androidserialport.h \
audioengine.h \
cbptc19696.h \
cgolay2087.h \
chamming.h \
codec.h \
codec2/codec2.h \
codec2/codec2_internal.h \
codec2/defines.h \
codec2/kiss_fft.h \
codec2/lpc.h \
codec2/nlp.h \
codec2/qbase.h \
codec2/quantise.h \
crs129.h \
dcscodec.h \
dmrcodec.h \
droidstar.h \
httpmanager.h \
iaxcodec.h \
iaxdefines.h \
m17codec.h \
nxdncodec.h \
p25codec.h \
refcodec.h \
serialambe.h \
serialmodem.h \
vocoder_plugin.h \
xrfcodec.h \
ysfcodec.h
macx:HEADERS += micpermission.h
contains(DEFINES, USE_FLITE){
LIBS += -lflite_cmu_us_slt -lflite_cmu_us_kal16 -lflite_cmu_us_awb -lflite_cmu_us_rms -lflite_usenglish -lflite_cmulex -lflite -lasound
}
ios:HEADERS += micpermission.h

@ -0,0 +1,488 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 5.0.2, 2021-11-02T16:12:08. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{1b40929c-f363-44e6-ae48-8bc2eb02f152}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
<value type="QString" key="ClangCodeModel.WarningConfigId">Builtin.BuildSystem</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">12</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.15.2 GCC 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.15.2 GCC 64bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5152.gcc_64_kit</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="int" key="EnableQmlDebugging">0</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Debug</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Release</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="int" key="QtQuickCompiler">0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
<value type="int" key="EnableQmlDebugging">0</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Profile</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="int" key="QtQuickCompiler">0</value>
<value type="int" key="SeparateDebugInfo">0</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="QString" key="Analyzer.Valgrind.SelfModifyingCodeDetection">2</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:/mnt/data/src/qt-projects/DroidStar/DroidStar.pro</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/mnt/data/src/qt-projects/DroidStar/DroidStar.pro</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Debug</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.1</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Android.Device.Type</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Android Qt 5.15.2 Clang Multi-Abi</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Android Qt 5.15.2 Clang Multi-Abi</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{955eaf34-57a0-4cce-b8ac-8097bc82fcb9}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">1</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="int" key="EnableQmlDebugging">0</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Debug</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis">
<value type="QString">armeabi-v7a</value>
</valuelist>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.2">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Copy application data</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.AndroidPackageInstallationStep</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.3">
<value type="QString" key="BuildTargetSdk">android-31</value>
<value type="QString" key="KeystoreLocation"></value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build Android APK</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmakeProjectManager.AndroidBuildApkStep</value>
<value type="bool" key="VerboseOutput">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">4</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Release</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis">
<value type="QString">armeabi-v7a</value>
<value type="QString">arm64-v8a</value>
</valuelist>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.2">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Copy application data</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.AndroidPackageInstallationStep</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.3">
<value type="QString" key="BuildTargetSdk">android-29</value>
<value type="QString" key="KeystoreLocation"></value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build Android APK</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmakeProjectManager.AndroidBuildApkStep</value>
<value type="bool" key="VerboseOutput">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">4</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="int" key="QtQuickCompiler">0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
<value type="int" key="EnableQmlDebugging">0</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Profile</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.2">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Copy application data</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.AndroidPackageInstallationStep</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.3">
<value type="QString" key="BuildTargetSdk">android-31</value>
<value type="QString" key="KeystoreLocation"></value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build Android APK</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmakeProjectManager.AndroidBuildApkStep</value>
<value type="bool" key="VerboseOutput">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">4</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="int" key="QtQuickCompiler">0</value>
<value type="int" key="SeparateDebugInfo">0</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.AndroidDeployQtStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.AndroidDeployConfiguration2</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings">
<valuelist type="QVariantList" key="AndroidDeviceAbis">
<value type="QString">arm64-v8a</value>
<value type="QString">armeabi-v7a</value>
<value type="QString">armeabi</value>
</valuelist>
<value type="QString" key="AndroidDeviceSerialNumber">R38N502YWQJ</value>
<value type="int" key="AndroidVersion.ApiLevel">30</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="QString" key="Analyzer.Valgrind.SelfModifyingCodeDetection">2</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Android.PostStartShellCmdListKey">
<value type="QString"></value>
</valuelist>
<valuelist type="QVariantList" key="Android.PreStartShellCmdListKey">
<value type="QString"></value>
</valuelist>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">0</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">DroidStar</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.AndroidRunConfiguration:/mnt/data/src/qt-projects/DroidStar/DroidStar.pro</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/mnt/data/src/qt-projects/DroidStar/DroidStar.pro</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">2</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,32 @@
/*
* Copyright (C) 2010,2016 by Jonathan Naylor G4KLX
*
* 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.
*/
#ifndef Golay24128_H
#define Golay24128_H
class CGolay24128 {
public:
static unsigned int encode23127(unsigned int data);
static unsigned int encode24128(unsigned int data);
static unsigned int decode23127(unsigned int code);
static unsigned int decode24128(unsigned int code);
static unsigned int decode24128(unsigned char* bytes);
};
#endif

@ -0,0 +1,66 @@
/*
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/>.
*/
import QtQuick 2.10
import QtQuick.Controls 2.3
Item {
id: hostsTab
property alias hostsTextEdit: hostsTxtEdit
Rectangle{
id: hostsList
x: 20
y: 20
width: parent.width - 40
height: parent.height - 40
color: "#252424"
Flickable{
anchors.fill: parent
contentWidth: parent.width
contentHeight: hostsTxtEdit.y +
hostsTxtEdit.height
flickableDirection: Flickable.VerticalFlick
clip: true
Text {
id: hostsText
width: hostsText.width
wrapMode: Text.WordWrap
color: "white"
text: qsTr("Custom hostfile format:\n" +
"<mode> <name> <host> <port> <password (optional)>\n" +
"Example: REF REF123 192.168.1.1 20001\n" +
"Example: DMR MyNet 192.168.1.1 62030 passw0rd")
}
TextArea {
id: hostsTxtEdit
x: 0
y: hostsText.height + 5
width: hostsList.width
height: 500
background: Rectangle {
color: "#000000"
radius: 5
}
wrapMode: TextArea.WordWrap
text: qsTr("")
onEditingFinished: {
droidstar.update_custom_hosts(hostsTxtEdit.text);
}
}
}
}
}

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>DroidStar</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.dudetronics.droidstar</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSMicrophoneUsageDescription</key>
<string>You know you want to...</string>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

@ -0,0 +1,59 @@
/*
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/>.
*/
import QtQuick 2.10
import QtQuick.Controls 2.3
Item {
id: logTab
property alias logText: logTxt
Button {
id: clearLogButton
x: 10
y: 5
width: 100
height: 30
text: qsTr("Clear")
onClicked: {
logTxt.clear();
}
}
Rectangle{
id: logTextBox
x: 20
y: 40
width: parent.width - 40
height: parent.height - 40
color: "#252424"
Flickable{
id: logflick
anchors.fill: parent
contentWidth: parent.width
contentHeight: logTxt.y +
logTxt.height
flickableDirection: Flickable.VerticalFlick
clip: true
TextArea {
id: logTxt
width: logTextBox.width
readOnly: true
wrapMode: TextArea.WordWrap
text: qsTr("")
}
}
}
}

@ -0,0 +1,593 @@
/*
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/>.
*/
import QtQuick 2.10
//import QtQuick.Window 2.15
import QtQuick.Controls 2.3
//import QtQuick.Dialogs 1.3
//import org.dudetronics.droidstar 1.0
Item {
id: mainTab
property alias element1: _element1
property alias element3: _element3
property alias element4: _element4
property alias label1: _label1
property alias label2: _label2
property alias label3: _label3
property alias label4: _label4
property alias label5: _label5
property alias label6: _label6
property alias status: _status
property alias levelMeter: _levelMeter
property alias uitimer: _uitimer
property alias comboMode: _comboMode
property alias comboHost: _comboHost
property alias editIAXDTMF: _editIAXDTMF
property alias dtmfsendbutton: _dtmfsendbutton
property alias comboModule: _comboModule
property alias dmrtgidEdit: _dmrtgidEdit
property alias privateBox: _privateBox
property alias connectbutton: _connectbutton
property alias data1: _data1
property alias data2: _data2
property alias data3: _data3
property alias data4: _data4
property alias data5: _data5
property alias data6: _data6
property alias txtimer: _txtimer
property alias buttonTX: _buttonTX
property alias btntxt: _btntxt
property alias swtxBox: _swtxBox
property alias swrxBox: _swrxBox
property alias agcBox: _agcBox
Text {
id: element
x: 10
y: 0
width: parent.width / 4
height: parent.height / 15;
text: qsTr("Mode")
color: "white"
font.pixelSize: parent.height / 30;
verticalAlignment: Text.AlignVCenter
}
Text {
id: _element1
x: 10
y: (parent.height / 15 + 3) * 1;
width: parent.width / 4
height: parent.height / 15;
text: qsTr("Host")
color: "white"
font.pixelSize: parent.height / 30;
verticalAlignment: Text.AlignVCenter
}
Text {
id: _element4
x: 10
y: (parent.height / 15 + 3) * 2;
width: parent.width / 5
height: parent.height / 15;
text: qsTr("Mod")
color: "white"
font.pixelSize: parent.height / 30;
verticalAlignment: Text.AlignVCenter
}
Text {
id: _element3
x: 10
y: (parent.height / 15 + 3) * 3;
width: parent.width / 4
height: parent.height / 15;
text: qsTr("TG ID")
color: "white"
font.pixelSize: parent.height / 30;
verticalAlignment: Text.AlignVCenter
}
Text {
id: _label1
x: 10
y: (parent.height / 15 + 3) * 4;
width: parent.width / 3
height: parent.height / 20;
text: qsTr("MYCALL")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _label2
x: 10
y: (parent.height / 15 + 3) * 5;
width: parent.width / 3
height: parent.height / 20;
text: qsTr("URCALL")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _label3
x: 10
y: (parent.height / 15 + 3) * 6;
width: parent.width / 3
height: parent.height / 20;
text: qsTr("RPTR1")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _label4
x: 10
y: (parent.height / 15 + 3) * 7;
width: parent.width / 3
height: parent.height / 20;
text: qsTr("RPTR2")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _label5
x: 10
y: (parent.height / 15 + 3) * 8;
width: parent.width / 3
height: parent.height / 20;
text: qsTr("StrmID")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _label6
x: 10
y: (parent.height / 15 + 3) * 9;
width: parent.width / 3
height: parent.height / 20;
text: qsTr("Text")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _status
x: 10
y: (parent.height / 15 + 3) * 10;
width: parent.width - 20
height: parent.height / 20;
text: qsTr("Not Connected")
color: "white"
font.pixelSize: parent.height / 35;
}
Rectangle {
x: 10
y: (parent.height / 15 + 3) * 11;
width: parent.width - 20
height: parent.height / 20;
color: "black"
border.color: "black"
border.width: 2
radius: 5
}
Rectangle {
id: _levelMeter
x: 10
y: (parent.height / 15 + 3) * 11;
width: 0
height: parent.height / 20;
color: "#80C342"
border.color: "black"
border.width: 2
radius: 5
}
Timer {
id: _uitimer
interval: 20; running: true; repeat: true
property var cnt: 0;
property var rxcnt: 0;
property var last_rxcnt: 0;
onTriggered: update_level();
function update_level(){
if(cnt >= 20){
if(rxcnt == last_rxcnt){
droidstar.set_output_level(0);
rxcnt = 0;
//console.log("TIMEOUT");
}
else{
last_rxcnt = rxcnt;
}
cnt = 0;
}
else{
++cnt;
}
var l = (parent.width - 20) * droidstar.get_output_level() / 32767.0;
if(l > _levelMeter.width){
_levelMeter.width = l;
}
else{
if(_levelMeter.width > 0)
_levelMeter.width -= 8;
else
_levelMeter.width = 0;
}
}
}
ComboBox {
id: _comboMode
property bool loaded: false
x: parent.width / 4
y: 0
width: (parent.width * 3 / 8) - 4
height: parent.height / 15;
font.pixelSize: parent.height / 35
model: ["M17", "YSF", "FCS", "DMR", "P25", "NXDN", "REF", "XRF", "DCS", "IAX"]
contentItem: Text {
text: _comboMode.displayText
font: _comboMode.font
leftPadding: 10
verticalAlignment: Text.AlignVCenter
color: _comboMode.enabled ? "white" : "darkgrey"
}
onCurrentTextChanged: {
if(_comboMode.loaded){
droidstar.process_mode_change(_comboMode.currentText);
}
}
}
ComboBox {
id: _comboHost
x: (parent.width / 4)
y: (parent.height / 15 + 3) * 1;
width: ((parent.width * 3) / 4) - 5
height: parent.height / 15;
font.pixelSize: parent.height / 35
contentItem: Text {
text: _comboHost.displayText
font: _comboHost.font
leftPadding: 10
verticalAlignment: Text.AlignVCenter
color: _comboHost.enabled ? "white" : "darkgrey"
}
onCurrentTextChanged: {
if(!droidstar.get_modelchange()){
droidstar.process_host_change(_comboHost.currentText);
}
}
}
TextField {
id: _editIAXDTMF
x: (parent.width / 4)
y: (parent.height / 15 + 3) * 1;
width: (parent.width * 3 / 8) - 4;
height: parent.height / 15;
font.pixelSize: parent.height / 35
inputMethodHints: "ImhPreferNumbers"
}
Button {
id: _dtmfsendbutton
x: (parent.width * 5 / 8)
y: (parent.height / 15 + 3) * 1;
width: (parent.width * 3 / 8) - 5;
height: parent.height / 15;
text: qsTr("Send")
font.pixelSize: parent.height / 30
onClicked: {
droidstar.dtmf_send_clicked(editIAXDTMF.text);
}
}
ComboBox {
id: _comboModule
x: parent.width / 5
y: (parent.height / 15 + 3) * 2
width: parent.width / 5
height: parent.height / 15;
font.pixelSize: parent.height / 35
model: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
contentItem: Text {
text: _comboModule.displayText
font: _comboModule.font
leftPadding: 10
verticalAlignment: Text.AlignVCenter
color: _comboModule.enabled ? "white" : "darkgrey"
}
onCurrentTextChanged: {
if(_comboMode.loaded){
droidstar.set_module(_comboModule.currentText);
}
}
}
CheckBox {
id: _swtxBox
x: (parent.width * 2 / 5)
y: (parent.height / 15 + 3) * 2;
width: parent.width / 4
height: parent.height / 15
text: qsTr("SWTX")
onClicked:{
droidstar.set_swtx(_swtxBox.checked)
}
}
CheckBox {
id: _swrxBox
x: (parent.width * 3 / 5)
y: (parent.height / 15 + 3) * 2;
width: parent.width / 4
height: parent.height / 15
text: qsTr("SWRX")
onClicked:{
droidstar.set_swrx(_swrxBox.checked)
}
}
CheckBox {
id: _agcBox
x: (parent.width * 4 / 5)
y: (parent.height / 15 + 3) * 2;
width: parent.width / 4
height: parent.height / 15
text: qsTr("AGC")
onClicked:{
droidstar.set_agc(_agcBox.checked)
}
}
TextField {
visible: false
id: _dmrtgidEdit
x: parent.width / 4
y: (parent.height / 15 + 3) * 3
width: (parent.width * 3 / 8) - 4
height: parent.height / 15
font.pixelSize: parent.height / 30
selectByMouse: true
inputMethodHints: "ImhPreferNumbers"
text: qsTr("")
onEditingFinished: {
droidstar.tgid_text_changed(dmrtgidEdit.text)
}
}
CheckBox {
id: _privateBox
x: (parent.width * 5 / 8)
y: (parent.height / 15 + 3) * 3;
width: (parent.width * 3 / 8) - 5
height: parent.height / 15
text: qsTr("Private")
onClicked:{
droidstar.set_dmr_pc(privateBox.checked)
//console.log("screen size ", parent.width, " x ", parent.height);
}
}
Button {
id: _connectbutton
x: (parent.width * 5 / 8)
y: 0
width: (parent.width * 3 / 8) - 5
height: parent.height / 15
text: qsTr("Connect")
font.pixelSize: parent.height / 30
onClicked: {
//settingsTab.callsignEdit.text = settingsTab.callsignEdit.text.toUpperCase();
droidstar.set_callsign(settingsTab.callsignEdit.text.toUpperCase());
//droidstar.set_host(comboHost.currentText);
droidstar.set_module(comboModule.currentText);
droidstar.set_protocol(comboMode.currentText);
droidstar.set_dmrtgid(dmrtgidEdit.text);
droidstar.set_dmrid(settingsTab.dmridEdit.text);
droidstar.set_essid(settingsTab.comboEssid.currentText);
droidstar.set_bm_password(settingsTab.bmpwEdit.text);
droidstar.set_tgif_password(settingsTab.tgifpwEdit.text);
droidstar.set_latitude(settingsTab.latEdit.text);
droidstar.set_longitude(settingsTab.lonEdit.text);
droidstar.set_location(settingsTab.locEdit.text);
droidstar.set_description(settingsTab.descEdit.text);
droidstar.set_url(settingsTab.urlEdit.text);
droidstar.set_swid(settingsTab.swidEdit.text);
droidstar.set_pkgid(settingsTab.pkgidEdit.text);
droidstar.set_dmr_options(settingsTab.dmroptsEdit.text);
droidstar.set_iaxuser(settingsTab.iaxuserEdit.text);
droidstar.set_iaxpass(settingsTab.iaxpassEdit.text);
droidstar.set_iaxnode(settingsTab.iaxnodeEdit.text);
droidstar.set_iaxhost(settingsTab.iaxhostEdit.text);
droidstar.set_iaxport(settingsTab.iaxportEdit.text);
droidstar.set_txtimeout(settingsTab.txtimerEdit.text);
//droidstar.set_toggletx(toggleTX.checked);
droidstar.set_xrf2ref(settingsTab.xrf2ref.checked);
droidstar.set_ipv6(settingsTab.ipv6.checked);
droidstar.set_vocoder(settingsTab.comboVocoder.currentText);
droidstar.set_modem(settingsTab.comboModem.currentText);
droidstar.set_playback(settingsTab.comboPlayback.currentText);
droidstar.set_capture(settingsTab.comboCapture.currentText);
droidstar.set_modemRxFreq(settingsTab.modemRXFreqEdit.text);
droidstar.set_modemTxFreq(settingsTab.modemTXFreqEdit.text);
droidstar.set_modemRxOffset(settingsTab.modemRXOffsetEdit.text);
droidstar.set_modemTxOffset(settingsTab.modemTXOffsetEdit.text);
droidstar.set_modemRxDCOffset(settingsTab.modemRXDCOffsetEdit.text);
droidstar.set_modemTxDCOffset(settingsTab.modemTXDCOffsetEdit.text);
droidstar.set_modemRxLevel(settingsTab.modemRXLevelEdit.text);
droidstar.set_modemTxLevel(settingsTab.modemRXLevelEdit.text);
droidstar.set_modemRFLevel(settingsTab.modemRFLevelEdit.text);
droidstar.set_modemTxDelay(settingsTab.modemTXDelayEdit.text);
droidstar.set_modemCWIdTxLevel(settingsTab.modemCWIdTXLevelEdit.text);
droidstar.set_modemDstarTxLevel(settingsTab.modemDStarTXLevelEdit.text);
droidstar.set_modemDMRTxLevel(settingsTab.modemDMRTXLevelEdit.text);
droidstar.set_modemYSFTxLevel(settingsTab.modemYSFTXLevelEdit.text);
droidstar.set_modemP25TxLevel(settingsTab.modemYSFTXLevelEdit.text);
droidstar.set_modemNXDNTxLevel(settingsTab.modemNXDNTXLevelEdit.text);
droidstar.process_connect();
}
}
Text {
id: _data1
x: parent.width / 3
y: (parent.height / 15 + 3) * 4;
width: (parent.width * 2) / 3
height: parent.height / 20;
text: qsTr("")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _data2
x: parent.width / 3
y: (parent.height / 15 + 3) * 5;
width: (parent.width * 2) / 3
height: parent.height / 20;
text: qsTr("")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _data3
x: parent.width / 3
y: (parent.height / 15 + 3) * 6;
width: (parent.width * 2) / 3
height: parent.height / 20;
text: qsTr("")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _data4
x: parent.width / 3
y: (parent.height / 15 + 3) * 7;
width: (parent.width * 2) / 3
height: parent.height / 20;
text: qsTr("")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _data5
x: parent.width / 3
y: (parent.height / 15 + 3) * 8;
width: (parent.width * 2) / 3
height: parent.height / 20;
text: qsTr("")
color: "white"
font.pixelSize: parent.height / 30;
}
Text {
id: _data6
x: parent.width / 3
y:(parent.height / 15 + 3) * 9;
width: (parent.width * 2) / 3
height: parent.height / 20;
text: qsTr("")
color: "white"
font.pixelSize: parent.height / 30;
}
Button {
Timer {
id: _txtimer
repeat: true;
onTriggered: {
++buttonTX.cnt;
btntxt.text = "TX: " + buttonTX.cnt;
if(buttonTX.cnt >= parseInt(settingsTab.txtimerEdit.text)){
buttonTX.tx = false;
droidstar.click_tx(buttonTX.tx);
_txtimer.running = false;
_btntxt.text = "TX";
}
}
}
property bool tx: false
property int cnt: 0
visible: true
enabled: false
id: _buttonTX
background: Rectangle {
color: _buttonTX.tx ? "#800000" : "steelblue" //"#80c342"
radius: 10
Text {
id:_btntxt
anchors.centerIn: parent
font.pointSize: 18
text: qsTr("TX")
}
}
x: 10
y: (parent.height / 15 + 3) * 12;
//y: parent.height - ((parent.height / 5) + 5);
width: parent.width - 20
height: parent.height - y - 10
//text: qsTr("TX")
font.pointSize: 24
onClicked: {
if(settingsTab.toggleTX.checked){
tx = !tx;
droidstar.click_tx(tx);
if(tx){
cnt = 0;
_txtimer.running = true;
_btntxt.color = "white";
}
else{
_txtimer.running = false;
btntxt.color = "black";
_btntxt.text = "TX";
}
}
}
onPressed: {
if(!settingsTab.toggleTX.checked){
tx = true;
droidstar.press_tx();
}
}
onReleased: {
if(!settingsTab.toggleTX.checked){
tx = false;
droidstar.release_tx();
}
}
onCanceled: {
if(!settingsTab.toggleTX.checked){
tx = false;
droidstar.release_tx();
}
}
}
}

@ -0,0 +1,89 @@
# DroidStar
This software connects to M17, Fusion (YSF/FCS, DN and VW modes are supported), DMR, P25, NXDN, D-STAR (REF/XRF/DCS) reflectors and AllStar nodes (as an IAX2 client) over UDP. It is compatible with all of the AMBE USB devices out there (ThumbDV, DVstick 30, DVSI, etc). It also supports MMDVM modems and can be used as a hotspot, or as a stand-alone transceiver via direct mode to the MMDVM device. This software is open source and uses the cross platform C++ library called Qt. It will build and run on Linux, Windows, MacOS, Android, and iOS. No USB device support for iOS though (AMBE vocoder or MMDVM). It should also build and run on any other posix platform that has Qt avilable (xxxBSD, Solaris, etc). This software is provided *as-is* and no support is available.
This software makes use of software from a number of other open source software projects, including MMDVMHost, MMDVM_CM, mvoice, and others. Not only is software from these projects being used directly, but learning about the various network protocols and encoding/decoding of the various protocols was only possible thanks to the authors of all of these software projects.
# DudeStar, DroidStar, and Qt
The DudeStar application used the Qt Widgets UI, while DroidStar uses the Qt Quick UI. All of the back end C/C++ source code for both projects has always been identical, but because of the different UI APIs, two repositories had to be maintained for the same project. Even though I prefer the Qt widgets UI over the Qt Quick UI for desktop applications, I have combined both projects into a single entity which is now simply called 'DroidStar'. My dudestar repo has been removed from github, but there are plenty of forks of it on github, in case anyone wishes to continue development of that version.
# M17 support
The Codec2 vocoder library is open source and is included as a C++ implementation of the original C library taken from the mvoice project. More info on M17 can be found here: https://m17project.org/
# MMDVM support -- work in progress
DroidStar supports MMDVM and MMDVM_HS (hotspot) modems, with basic (possibly buggy) support for D-STAR, Fusion, and DMR. Support for M17, P25, and NXDN coming soon. When connecting to a digital mode reflector/DMR server and selecting an MMDVM device under Modems, then DroidStar acts as a hotspot/repeater. When 'MMDVM Direct' is selected as the reflector/server, then DroidStar becomes a stand-alone transceiver.
# Software vocoder plugin API
There is a vocoder plugin API available for loading of vocoder software. Any vocoder plugin used with DroidStar should be properly licensed by the user if any copyright patents apply. Do not use any patented vocoder plugin that you are not licensed to use. I have no information regarding aquiring a software vocoder.
# Loading a vocoder plugin
A vocoder plugin is placed in the standard Download location for the given platform:
Linux: ~/Downloads
MacOS: ~/Downloads
Windows: C:/Users/<USER>/Documents
Android: /storage/emulated/0/Download (typically referred to as Internal storage -> Download
The vocoder plugin filename must be named vocoder_plugin.<platform>.<arch> where platform and arch can be any of the following:
platform: linux, darwin, winnt, android, ios
arch: x86_64, arm, arm64
There are no software vocoder plugins available in this repository.
# Optional FLite Text-to-speech build
I added Flite TTS TX capability so I didn't have to talk to myself all of the time during development and testing. To build DroidStar with Flite TTS support, uncomment the line 'DEFINES += USE_FLITE' from the top of DroidStar.pro (and run/re-run qmake). You will need the Flite library and development header files installed on your system. When built with Flite support, 3 TTS options and a Mic in option will be available at the bottom of the window. TTS1-TTS3 are 3 voice choices, and Mic in turns off TTS and uses the microphone for input. The text to be converted to speech and transmitted goes in the text box under the TTS options.
# Usage
Linux users with USB AMBE and/or MMDVM dongles will need to make sure they have permission to use the USB serial device, and disable the archaic ModeManager service that still exists on many Linux systems. On most systems this means adding your user to the 'dialout' group, and running 'sudo systemctl disable ModemManager.service' and rebooting. This is a requirement for any serial device to be accessed.
Host/Mod: Select the desired host and module (for D-STAR and M17) from the selections.
Callsign: Enter your amateur radio callsign. A valid license is required to use this software.
DMRID: A valid DMR ID is required to connect to DMR servers.
Latitude/Longitude/Location/Description: These are DMR config options, sent to the DMR server during connect. Some servers require specific values here, some do not. This is specific to the server you are connecting to, so please dont ask what these values should be.
DMR+ IPSC2 hosts: The format for the DMR+ options string is the complete string including 'Options='. Create your options string and check 'Send DMR+ options on connect' before connecting. A description of the DMR+ options string can be found here: https://github.com/g4klx/MMDVMHost/blob/master/DMRplus_startup_options.md .
Talkgroup: For DMR, enter the talkgroup ID number. A very active TG for testing functionality on Brandmeister is 91 (Brandmeister Worldwide). You must TX with a talkgroup entered to link to that talkgroup, just like a real radio. Any ststics you have defined in BM selfcare will work the same way they do if you were using a hotspot/radio.
MYCALL/URCALL/RPTR1/RPTR2 are for Dstar modes REF/DCS/XRF. These fields need to be entered correctly before attempting to TX on any DSTAR reflector. All fields are populated with suggested values upon connect, but can still be modified for advanced users. RPT2 is always overwritten with the current reflector upon connected.
# IAX Client for AllStar
Dudestar can connect to an AllStar node as an IAX(2) client. See the AllStar wiki and other AllStar, Asterisk, and IAX2 protocal related websites for the technical details of IAX2 for AllStar. This is a basic client and currently only uLaw audio codec is supported. This is the default codec on most AllStar nodes.
Username: Defined in your nodes iax.conf file, usually iaxclient
Password: Defined as 'secret' in your iax.conf
Node[@Context]: ID and context of your AllStar node. The context is optional and if not specified, defaults to iax-client.
Host: hostname or IP address of node.
Port: UDP port of node, usually 4569.
Add DTMF commands like \*3node, \*1node, \*70, etc in the IAX DTMF box and hit send to send the DTMF string. The asterisk (*) character is already added on the Droidstar app, so only input the numeric portion of the command (70 instead of *70, etc). Details on various commands can be found at the AllStar wiki and others.
# Building
This software is written primarily in C++ on Linux and requires Qt5, and natually the devel packages to build. Java, QML (Javascript based), and C# code is also used where necessary.
Qt5 and Qt5-Quick development packages are required to build this software from source. With these requirements met, run the following:
```
qmake
make
```
qmake may have a different name on your distribution i.e. on Fedora it's called qmake-qt5
Notes for building/running Debian/Raspbian: In addition to the Linux build requirements, there are some additional requirements for running this QT application in order for the audio devices to be correctly detected:
```
sudo apt-get install libqt5multimedia5-plugins libqt5serialport5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5
```
And if pulseaudio is not currently installed:
```
sudo apt-get install pulseaudio
```
My primary development platform is Fedora Linux. With a proper build environment, the build instructions apply to all other platforms/distributions, including Windows and macOS.
All of the gradle build files are provided to create an APK file ready to be installed on an Android device. A proper Android build system including the Android NDK is required and beyond the scope of this document.
# No builds are available
No builds for any platform are available here. This is and always will be an open source project, to be used for educational and development purposes only.

@ -0,0 +1,373 @@
/*
* Copyright (C) 2005, 2006, 2008 Free Software Foundation, Inc.
* Copyright (C) 2011,2015 by Jonathan Naylor G4KLX
*
* 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 "SHA256.h"
#include <cstdio>
#include <cstring>
#include <cassert>
#ifdef WORDS_BIGENDIAN
# define SWAP(n) (n)
#else
# define SWAP(n) \
(((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
#endif
#define BLOCKSIZE 4096
#if BLOCKSIZE % 64 != 0
# error "invalid BLOCKSIZE"
#endif
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. */
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
/*
Takes a pointer to a 256 bit block of data (eight 32 bit ints) and
intializes it to the start constants of the SHA256 algorithm. This
must be called before using hash in the call to sha256_hash
*/
CSHA256::CSHA256() :
m_state(NULL),
m_total(NULL),
m_buflen(0U),
m_buffer(NULL)
{
m_state = new uint32_t[8U];
m_total = new uint32_t[2U];
m_buffer = new uint32_t[32U];
init();
}
CSHA256::~CSHA256()
{
delete[] m_state;
delete[] m_total;
delete[] m_buffer;
}
void CSHA256::init()
{
m_state[0] = 0x6a09e667UL;
m_state[1] = 0xbb67ae85UL;
m_state[2] = 0x3c6ef372UL;
m_state[3] = 0xa54ff53aUL;
m_state[4] = 0x510e527fUL;
m_state[5] = 0x9b05688cUL;
m_state[6] = 0x1f83d9abUL;
m_state[7] = 0x5be0cd19UL;
m_total[0] = m_total[1] = 0;
m_buflen = 0;
}
/* Copy the value from v into the memory location pointed to by *cp,
If your architecture allows unaligned access this is equivalent to
* (uint32_t *) cp = v */
static inline void set_uint32(unsigned char* cp, uint32_t v)
{
assert(cp != NULL);
::memcpy(cp, &v, sizeof v);
}
/* Put result from CTX in first 32 bytes following RESBUF. The result
must be in little endian byte order. */
unsigned char* CSHA256::read(unsigned char* resbuf)
{
assert(resbuf != NULL);
for (unsigned int i = 0U; i < 8U; i++)
set_uint32(resbuf + i * sizeof(m_state[0]), SWAP(m_state[i]));
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF. */
void CSHA256::conclude()
{
/* Take yet unprocessed bytes into account. */
unsigned int bytes = m_buflen;
unsigned int size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;
/* Now count remaining bytes. */
m_total[0] += bytes;
if (m_total[0] < bytes)
++m_total[1];
/* Put the 64-bit file length in *bits* at the end of the buffer.
Use set_uint32 rather than a simple assignment, to avoid risk of
unaligned access. */
set_uint32((unsigned char*)&m_buffer[size - 2], SWAP((m_total[1] << 3) | (m_total[0] >> 29)));
set_uint32((unsigned char*)&m_buffer[size - 1], SWAP(m_total[0] << 3));
::memcpy(&((char*)m_buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
/* Process last bytes. */
processBlock((unsigned char*)m_buffer, size * 4);
}
unsigned char* CSHA256::finish(unsigned char* resbuf)
{
assert(resbuf != NULL);
conclude();
return read(resbuf);
}
/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
unsigned char* CSHA256::buffer(const unsigned char* buffer, unsigned int len, unsigned char* resblock)
{
assert(buffer != NULL);
assert(resblock != NULL);
/* Initialize the computation context. */
init();
/* Process whole buffer but last len % 64 bytes. */
processBytes(buffer, len);
/* Put result in desired memory area. */
return finish(resblock);
}
void CSHA256::processBytes(const unsigned char* buffer, unsigned int len)
{
assert(buffer != NULL);
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if (m_buflen != 0U) {
unsigned int left_over = m_buflen;
unsigned int add = 128U - left_over > len ? len : 128U - left_over;
::memcpy(&((char*)m_buffer)[left_over], buffer, add);
m_buflen += add;
if (m_buflen > 64U) {
processBlock((unsigned char*)m_buffer, m_buflen & ~63U);
m_buflen &= 63U;
/* The regions in the following copy operation cannot overlap. */
::memcpy(m_buffer, &((char*)m_buffer)[(left_over + add) & ~63U], m_buflen);
}
buffer += add;
len -= add;
}
/* Process available complete blocks. */
if (len >= 64U) {
//#if !_STRING_ARCH_unaligned
//# define alignof(type) offsetof (struct { char c; type x; }, x)
//# define UNALIGNED_P(p) (((unsigned int) p) % alignof (uint32_t) != 0)
// if (UNALIGNED_P (buffer)) {
// while (len > 64U) {
// ::memcpy(m_buffer, buffer, 64U);
// processBlock((unsigned char*)m_buffer, 64U);
// buffer += 64U;
// len -= 64U;
// }
// } else
//#endif
{
processBlock(buffer, len & ~63U);
buffer += (len & ~63U);
len &= 63U;
}
}
/* Move remaining bytes in internal buffer. */
if (len > 0U) {
unsigned int left_over = m_buflen;
::memcpy(&((char*)m_buffer)[left_over], buffer, len);
left_over += len;
if (left_over >= 64U) {
processBlock((unsigned char*)m_buffer, 64U);
left_over -= 64U;
::memcpy(m_buffer, &m_buffer[16], left_over);
}
m_buflen = left_over;
}
}
/* --- Code below is the primary difference between sha1.c and sha256.c --- */
/* SHA256 round constants */
#define K(I) roundConstants[I]
static const uint32_t roundConstants[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL,
0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL,
0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL,
0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL,
0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL,
0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL,
0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL,
};
/* Round functions. */
#define F2(A,B,C) ( ( A & B ) | ( C & ( A | B ) ) )
#define F1(E,F,G) ( G ^ ( E & ( F ^ G ) ) )
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0.
Most of this code comes from GnuPG's cipher/sha1.c. */
void CSHA256::processBlock(const unsigned char* buffer, unsigned int len)
{
assert(buffer != NULL);
const uint32_t* words = (uint32_t*)buffer;
unsigned int nwords = len / sizeof(uint32_t);
const uint32_t* endp = words + nwords;
uint32_t x[16];
uint32_t a = m_state[0];
uint32_t b = m_state[1];
uint32_t c = m_state[2];
uint32_t d = m_state[3];
uint32_t e = m_state[4];
uint32_t f = m_state[5];
uint32_t g = m_state[6];
uint32_t h = m_state[7];
/* First increment the byte count. FIPS PUB 180-2 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. Do a double word increment. */
m_total[0] += len;
if (m_total[0] < len)
++m_total[1];
#define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define S0(x) (rol(x,25)^rol(x,14)^(x>>3))
#define S1(x) (rol(x,15)^rol(x,13)^(x>>10))
#define SS0(x) (rol(x,30)^rol(x,19)^rol(x,10))
#define SS1(x) (rol(x,26)^rol(x,21)^rol(x,7))
#define M(I) (tm = S1(x[(I-2)&0x0f]) + x[(I-7)&0x0f] + S0(x[(I-15)&0x0f]) + x[I&0x0f], x[I&0x0f] = tm)
#define R(A,B,C,D,E,F,G,H,K,M) do { t0 = SS0(A) + F2(A,B,C); \
t1 = H + SS1(E) + F1(E,F,G) + K + M; \
D += t1; H = t0 + t1; \
} while(0)
while (words < endp) {
uint32_t tm;
uint32_t t0, t1;
/* FIXME: see sha1.c for a better implementation. */
for (unsigned int t = 0U; t < 16U; t++) {
x[t] = SWAP(*words);
words++;
}
R( a, b, c, d, e, f, g, h, K( 0), x[ 0] );
R( h, a, b, c, d, e, f, g, K( 1), x[ 1] );
R( g, h, a, b, c, d, e, f, K( 2), x[ 2] );
R( f, g, h, a, b, c, d, e, K( 3), x[ 3] );
R( e, f, g, h, a, b, c, d, K( 4), x[ 4] );
R( d, e, f, g, h, a, b, c, K( 5), x[ 5] );
R( c, d, e, f, g, h, a, b, K( 6), x[ 6] );
R( b, c, d, e, f, g, h, a, K( 7), x[ 7] );
R( a, b, c, d, e, f, g, h, K( 8), x[ 8] );
R( h, a, b, c, d, e, f, g, K( 9), x[ 9] );
R( g, h, a, b, c, d, e, f, K(10), x[10] );
R( f, g, h, a, b, c, d, e, K(11), x[11] );
R( e, f, g, h, a, b, c, d, K(12), x[12] );
R( d, e, f, g, h, a, b, c, K(13), x[13] );
R( c, d, e, f, g, h, a, b, K(14), x[14] );
R( b, c, d, e, f, g, h, a, K(15), x[15] );
R( a, b, c, d, e, f, g, h, K(16), M(16) );
R( h, a, b, c, d, e, f, g, K(17), M(17) );
R( g, h, a, b, c, d, e, f, K(18), M(18) );
R( f, g, h, a, b, c, d, e, K(19), M(19) );
R( e, f, g, h, a, b, c, d, K(20), M(20) );
R( d, e, f, g, h, a, b, c, K(21), M(21) );
R( c, d, e, f, g, h, a, b, K(22), M(22) );
R( b, c, d, e, f, g, h, a, K(23), M(23) );
R( a, b, c, d, e, f, g, h, K(24), M(24) );
R( h, a, b, c, d, e, f, g, K(25), M(25) );
R( g, h, a, b, c, d, e, f, K(26), M(26) );
R( f, g, h, a, b, c, d, e, K(27), M(27) );
R( e, f, g, h, a, b, c, d, K(28), M(28) );
R( d, e, f, g, h, a, b, c, K(29), M(29) );
R( c, d, e, f, g, h, a, b, K(30), M(30) );
R( b, c, d, e, f, g, h, a, K(31), M(31) );
R( a, b, c, d, e, f, g, h, K(32), M(32) );
R( h, a, b, c, d, e, f, g, K(33), M(33) );
R( g, h, a, b, c, d, e, f, K(34), M(34) );
R( f, g, h, a, b, c, d, e, K(35), M(35) );
R( e, f, g, h, a, b, c, d, K(36), M(36) );
R( d, e, f, g, h, a, b, c, K(37), M(37) );
R( c, d, e, f, g, h, a, b, K(38), M(38) );
R( b, c, d, e, f, g, h, a, K(39), M(39) );
R( a, b, c, d, e, f, g, h, K(40), M(40) );
R( h, a, b, c, d, e, f, g, K(41), M(41) );
R( g, h, a, b, c, d, e, f, K(42), M(42) );
R( f, g, h, a, b, c, d, e, K(43), M(43) );
R( e, f, g, h, a, b, c, d, K(44), M(44) );
R( d, e, f, g, h, a, b, c, K(45), M(45) );
R( c, d, e, f, g, h, a, b, K(46), M(46) );
R( b, c, d, e, f, g, h, a, K(47), M(47) );
R( a, b, c, d, e, f, g, h, K(48), M(48) );
R( h, a, b, c, d, e, f, g, K(49), M(49) );
R( g, h, a, b, c, d, e, f, K(50), M(50) );
R( f, g, h, a, b, c, d, e, K(51), M(51) );
R( e, f, g, h, a, b, c, d, K(52), M(52) );
R( d, e, f, g, h, a, b, c, K(53), M(53) );
R( c, d, e, f, g, h, a, b, K(54), M(54) );
R( b, c, d, e, f, g, h, a, K(55), M(55) );
R( a, b, c, d, e, f, g, h, K(56), M(56) );
R( h, a, b, c, d, e, f, g, K(57), M(57) );
R( g, h, a, b, c, d, e, f, K(58), M(58) );
R( f, g, h, a, b, c, d, e, K(59), M(59) );
R( e, f, g, h, a, b, c, d, K(60), M(60) );
R( d, e, f, g, h, a, b, c, K(61), M(61) );
R( c, d, e, f, g, h, a, b, K(62), M(62) );
R( b, c, d, e, f, g, h, a, K(63), M(63) );
a = m_state[0] += a;
b = m_state[1] += b;
c = m_state[2] += c;
d = m_state[3] += d;
e = m_state[4] += e;
f = m_state[5] += f;
g = m_state[6] += g;
h = m_state[7] += h;
}
}

@ -0,0 +1,73 @@
/*
* Copyright (C) 2005, 2006, 2008, 2009 Free Software Foundation, Inc.
* Copyright (C) 2011,2015,2016 by Jonathan Naylor G4KLX
*
* 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.
*/
#ifndef SHA256_H
#define SHA256_H
#include <cstdint>
enum {
SHA256_DIGEST_SIZE = 256 / 8
};
class CSHA256 {
public:
CSHA256();
~CSHA256();
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is necessary that LEN is a multiple of 64!!! */
void processBlock(const unsigned char* buffer, unsigned int len);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is NOT required that LEN is a multiple of 64. */
void processBytes(const unsigned char* buffer, unsigned int len);
/* Process the remaining bytes in the buffer and put result from CTX
in first 32 bytes following RESBUF. The result is always in little
endian byte order, so that a byte-wise output yields to the wanted
ASCII representation of the message digest. */
unsigned char* finish(unsigned char* resbuf);
/* Put result from CTX in first 32 bytes following RESBUF. The result is
always in little endian byte order, so that a byte-wise output yields
to the wanted ASCII representation of the message digest. */
unsigned char* read(unsigned char* resbuf);
/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
unsigned char* buffer(const unsigned char* buffer, unsigned int len, unsigned char* resblock);
private:
uint32_t* m_state;
uint32_t* m_total;
unsigned int m_buflen;
uint32_t* m_buffer;
void init();
void conclude();
};
#endif

File diff suppressed because it is too large Load Diff

@ -0,0 +1,141 @@
/*
* Copyright (C) 2009-2016 by Jonathan Naylor G4KLX
*
* 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 "YSFConvolution.h"
#include <cstdio>
#include <cassert>
#include <cstring>
const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U};
#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
const uint8_t BRANCH_TABLE1[] = {0U, 0U, 0U, 0U, 1U, 1U, 1U, 1U};
const uint8_t BRANCH_TABLE2[] = {0U, 1U, 1U, 0U, 0U, 1U, 1U, 0U};
const unsigned int NUM_OF_STATES_D2 = 8U;
const unsigned int NUM_OF_STATES = 16U;
const uint32_t M = 2U;
const unsigned int K = 5U;
CYSFConvolution::CYSFConvolution() :
m_metrics1(nullptr),
m_metrics2(nullptr),
m_oldMetrics(nullptr),
m_newMetrics(nullptr),
m_decisions(nullptr),
m_dp(nullptr)
{
m_metrics1 = new uint16_t[16U];
m_metrics2 = new uint16_t[16U];
m_decisions = new uint64_t[180U];
}
CYSFConvolution::~CYSFConvolution()
{
delete[] m_metrics1;
delete[] m_metrics2;
delete[] m_decisions;
}
void CYSFConvolution::start()
{
::memset(m_metrics1, 0x00U, NUM_OF_STATES * sizeof(uint16_t));
::memset(m_metrics2, 0x00U, NUM_OF_STATES * sizeof(uint16_t));
m_oldMetrics = m_metrics1;
m_newMetrics = m_metrics2;
m_dp = m_decisions;
}
void CYSFConvolution::decode(uint8_t s0, uint8_t s1)
{
*m_dp = 0U;
for (uint8_t i = 0U; i < NUM_OF_STATES_D2; i++) {
uint8_t j = i * 2U;
uint16_t metric = (BRANCH_TABLE1[i] ^ s0) + (BRANCH_TABLE2[i] ^ s1);
uint16_t m0 = m_oldMetrics[i] + metric;
uint16_t m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + (M - metric);
uint8_t decision0 = (m0 >= m1) ? 1U : 0U;
m_newMetrics[j + 0U] = decision0 != 0U ? m1 : m0;
m0 = m_oldMetrics[i] + (M - metric);
m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + metric;
uint8_t decision1 = (m0 >= m1) ? 1U : 0U;
m_newMetrics[j + 1U] = decision1 != 0U ? m1 : m0;
*m_dp |= (uint64_t(decision1) << (j + 1U)) | (uint64_t(decision0) << (j + 0U));
}
++m_dp;
assert((m_dp - m_decisions) <= 180);
uint16_t* tmp = m_oldMetrics;
m_oldMetrics = m_newMetrics;
m_newMetrics = tmp;
}
void CYSFConvolution::chainback(unsigned char* out, unsigned int nBits)
{
assert(out != NULL);
uint32_t state = 0U;
while (nBits-- > 0) {
--m_dp;
uint32_t i = state >> (9 - K);
uint8_t bit = uint8_t(*m_dp >> i) & 1;
state = (bit << 7) | (state >> 1);
WRITE_BIT1(out, nBits, bit != 0U);
}
}
void CYSFConvolution::encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const
{
assert(in != NULL);
assert(out != NULL);
assert(nBits > 0U);
uint8_t d1 = 0U, d2 = 0U, d3 = 0U, d4 = 0U;
uint32_t k = 0U;
for (unsigned int i = 0U; i < nBits; i++) {
uint8_t d = READ_BIT1(in, i) ? 1U : 0U;
uint8_t g1 = (d + d3 + d4) & 1;
uint8_t g2 = (d + d1 + d2 + d4) & 1;
d4 = d3;
d3 = d2;
d2 = d1;
d1 = d;
WRITE_BIT1(out, k, g1 != 0U);
k++;
WRITE_BIT1(out, k, g2 != 0U);
k++;
}
}

@ -0,0 +1,45 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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.
*/
#if !defined(YSFConvolution_H)
#define YSFConvolution_H
#include <cstdint>
class CYSFConvolution {
public:
CYSFConvolution();
~CYSFConvolution();
void start();
void decode(uint8_t s0, uint8_t s1);
void chainback(unsigned char* out, unsigned int nBits);
void encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const;
private:
uint16_t* m_metrics1;
uint16_t* m_metrics2;
uint16_t* m_oldMetrics;
uint16_t* m_newMetrics;
uint64_t* m_decisions;
uint64_t* m_dp;
};
#endif

@ -0,0 +1,323 @@
/*
* Copyright (C) 2009-2016 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
*
* 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 "YSFConvolution.h"
#include "Golay24128.h"
#include "YSFFICH.h"
#include "CRCenc.h"
#include <cstdio>
#include <cassert>
#include <cstring>
const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U};
#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
const unsigned int INTERLEAVE_TABLE[] = {
0U, 40U, 80U, 120U, 160U,
2U, 42U, 82U, 122U, 162U,
4U, 44U, 84U, 124U, 164U,
6U, 46U, 86U, 126U, 166U,
8U, 48U, 88U, 128U, 168U,
10U, 50U, 90U, 130U, 170U,
12U, 52U, 92U, 132U, 172U,
14U, 54U, 94U, 134U, 174U,
16U, 56U, 96U, 136U, 176U,
18U, 58U, 98U, 138U, 178U,
20U, 60U, 100U, 140U, 180U,
22U, 62U, 102U, 142U, 182U,
24U, 64U, 104U, 144U, 184U,
26U, 66U, 106U, 146U, 186U,
28U, 68U, 108U, 148U, 188U,
30U, 70U, 110U, 150U, 190U,
32U, 72U, 112U, 152U, 192U,
34U, 74U, 114U, 154U, 194U,
36U, 76U, 116U, 156U, 196U,
38U, 78U, 118U, 158U, 198U};
const unsigned int YSF_SYNC_LENGTH_BYTES = 5U;
CYSFFICH::CYSFFICH()
//m_fich(NULL)
{
m_fich[0] = 0;
m_fich[1] = 0;
m_fich[2] = 0;
m_fich[3] = 0;
m_fich[4] = 0;
m_fich[5] = 0;
}
CYSFFICH::~CYSFFICH()
{
//delete[] m_fich;
}
bool CYSFFICH::decode(const unsigned char* bytes)
{
assert(bytes != NULL);
// Skip the sync bytes
bytes += YSF_SYNC_LENGTH_BYTES;
CYSFConvolution viterbi;
viterbi.start();
// Deinterleave the FICH and send bits to the Viterbi decoder
for (unsigned int i = 0U; i < 100U; i++) {
unsigned int n = INTERLEAVE_TABLE[i];
uint8_t s0 = READ_BIT1(bytes, n) ? 1U : 0U;
n++;
uint8_t s1 = READ_BIT1(bytes, n) ? 1U : 0U;
viterbi.decode(s0, s1);
}
unsigned char output[13U];
viterbi.chainback(output, 96U);
unsigned int b0 = CGolay24128::decode24128(output + 0U);
unsigned int b1 = CGolay24128::decode24128(output + 3U);
unsigned int b2 = CGolay24128::decode24128(output + 6U);
unsigned int b3 = CGolay24128::decode24128(output + 9U);
m_fich[0U] = (b0 >> 4) & 0xFFU;
m_fich[1U] = ((b0 << 4) & 0xF0U) | ((b1 >> 8) & 0x0FU);
m_fich[2U] = (b1 >> 0) & 0xFFU;
m_fich[3U] = (b2 >> 4) & 0xFFU;
m_fich[4U] = ((b2 << 4) & 0xF0U) | ((b3 >> 8) & 0x0FU);
m_fich[5U] = (b3 >> 0) & 0xFFU;
return CCRC::checkCCITT162(m_fich, 6U);
}
void CYSFFICH::encode(unsigned char* bytes)
{
assert(bytes != NULL);
// Skip the sync bytes
bytes += YSF_SYNC_LENGTH_BYTES;
CCRC::addCCITT162(m_fich, 6U);
unsigned int b0 = ((m_fich[0U] << 4) & 0xFF0U) | ((m_fich[1U] >> 4) & 0x00FU);
unsigned int b1 = ((m_fich[1U] << 8) & 0xF00U) | ((m_fich[2U] >> 0) & 0x0FFU);
unsigned int b2 = ((m_fich[3U] << 4) & 0xFF0U) | ((m_fich[4U] >> 4) & 0x00FU);
unsigned int b3 = ((m_fich[4U] << 8) & 0xF00U) | ((m_fich[5U] >> 0) & 0x0FFU);
unsigned int c0 = CGolay24128::encode24128(b0);
unsigned int c1 = CGolay24128::encode24128(b1);
unsigned int c2 = CGolay24128::encode24128(b2);
unsigned int c3 = CGolay24128::encode24128(b3);
unsigned char conv[13U];
conv[0U] = (c0 >> 16) & 0xFFU;
conv[1U] = (c0 >> 8) & 0xFFU;
conv[2U] = (c0 >> 0) & 0xFFU;
conv[3U] = (c1 >> 16) & 0xFFU;
conv[4U] = (c1 >> 8) & 0xFFU;
conv[5U] = (c1 >> 0) & 0xFFU;
conv[6U] = (c2 >> 16) & 0xFFU;
conv[7U] = (c2 >> 8) & 0xFFU;
conv[8U] = (c2 >> 0) & 0xFFU;
conv[9U] = (c3 >> 16) & 0xFFU;
conv[10U] = (c3 >> 8) & 0xFFU;
conv[11U] = (c3 >> 0) & 0xFFU;
conv[12U] = 0x00U;
CYSFConvolution convolution;
unsigned char convolved[25U];
convolution.encode(conv, convolved, 100U);
unsigned int j = 0U;
for (unsigned int i = 0U; i < 100U; i++) {
unsigned int n = INTERLEAVE_TABLE[i];
bool s0 = READ_BIT1(convolved, j) != 0U;
j++;
bool s1 = READ_BIT1(convolved, j) != 0U;
j++;
WRITE_BIT1(bytes, n, s0);
n++;
WRITE_BIT1(bytes, n, s1);
}
}
unsigned char CYSFFICH::getFI() const
{
return (m_fich[0U] >> 6) & 0x03U;
}
unsigned char CYSFFICH::getCS() const
{
return (m_fich[0U] >> 4) & 0x03U;
}
unsigned char CYSFFICH::getCM() const
{
return (m_fich[0U] >> 2) & 0x03U;
}
unsigned char CYSFFICH::getBN() const
{
return m_fich[0U] & 0x03U;
}
unsigned char CYSFFICH::getBT() const
{
return (m_fich[1U] >> 6) & 0x03U;
}
unsigned char CYSFFICH::getFN() const
{
return (m_fich[1U] >> 3) & 0x07U;
}
unsigned char CYSFFICH::getFT() const
{
return m_fich[1U] & 0x07U;
}
unsigned char CYSFFICH::getDT() const
{
return m_fich[2U] & 0x03U;
}
unsigned char CYSFFICH::getMR() const
{
return (m_fich[2U] >> 3) & 0x03U;
}
bool CYSFFICH::getVoIP() const
{
return (m_fich[2U] & 0x04U) == 0x04U;
}
bool CYSFFICH::getDev() const
{
return (m_fich[2U] & 0x40U) == 0x40U;
}
bool CYSFFICH::getSQL() const
{
return (m_fich[3U] & 0x80U) == 0x80U;
}
unsigned char CYSFFICH::getSQ() const
{
return m_fich[3U] & 0x7FU;
}
void CYSFFICH::setFI(unsigned char fi)
{
m_fich[0U] &= 0x3FU;
m_fich[0U] |= (fi << 6) & 0xC0U;
}
void CYSFFICH::setCS(unsigned char cs)
{
m_fich[0U] &= 0xCFU;
m_fich[0U] |= (cs << 4) & 0x30U;
}
void CYSFFICH::setCM(unsigned char cm)
{
m_fich[0U] &= 0xF3U;
m_fich[0U] |= (cm << 2) & 0x0CU;
}
void CYSFFICH::setFN(unsigned char fn)
{
m_fich[1U] &= 0xC7U;
m_fich[1U] |= (fn << 3) & 0x38U;
}
void CYSFFICH::setFT(unsigned char ft)
{
m_fich[1U] &= 0xF8U;
m_fich[1U] |= ft & 0x07U;
}
void CYSFFICH::setMR(unsigned char mr)
{
m_fich[2U] &= 0xC7U;
m_fich[2U] |= (mr << 3) & 0x38U;
}
void CYSFFICH::setVoIP(bool on)
{
if (on)
m_fich[2U] |= 0x04U;
else
m_fich[2U] &= 0xFBU;
}
void CYSFFICH::setDev(bool on)
{
if (on)
m_fich[2U] |= 0x40U;
else
m_fich[2U] &= 0xBFU;
}
void CYSFFICH::setDT(unsigned char dt)
{
m_fich[2U] &= 0xFCU;
m_fich[2U] |= dt & 0x03U;
}
void CYSFFICH::setSQL(bool on)
{
if (on)
m_fich[3U] |= 0x80U;
else
m_fich[3U] &= 0x7FU;
}
void CYSFFICH::setSQ(unsigned char sq)
{
m_fich[3U] &= 0x80U;
m_fich[3U] |= sq & 0x7FU;
}
void CYSFFICH::setBN(unsigned char bn)
{
m_fich[0U] &= 0xFCU;
m_fich[0U] |= bn & 0x03U;
}
void CYSFFICH::setBT(unsigned char bt)
{
m_fich[1U] &= 0x3FU;
m_fich[1U] |= (bt << 6) & 0xC0U;
}
void CYSFFICH::load(const unsigned char* fich)
{
assert(fich != NULL);
::memcpy(m_fich, fich, 4U);
}

@ -0,0 +1,67 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
*
* 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.
*/
#if !defined(YSFFICH_H)
#define YSFFICH_H
class CYSFFICH {
public:
CYSFFICH();
~CYSFFICH();
bool decode(const unsigned char* bytes);
void encode(unsigned char* bytes);
unsigned char getFI() const;
unsigned char getCS() const;
unsigned char getCM() const;
unsigned char getBN() const;
unsigned char getBT() const;
unsigned char getFN() const;
unsigned char getFT() const;
unsigned char getDT() const;
unsigned char getMR() const;
bool getVoIP() const;
bool getDev() const;
bool getSQL() const;
unsigned char getSQ() const;
void setFI(unsigned char fi);
void setCS(unsigned char cs);
void setCM(unsigned char cm);
void setFN(unsigned char fn);
void setFT(unsigned char ft);
void setBN(unsigned char bn);
void setBT(unsigned char bt);
void setDT(unsigned char dt);
void setMR(unsigned char mr);
void setVoIP(bool set);
void setDev(bool set);
void setSQL(bool set);
void setSQ(unsigned char sq);
void load(const unsigned char* fich);
private:
unsigned char m_fich[6U];
//m_fich = new unsigned char[6U];
};
#endif

@ -0,0 +1,85 @@
<?xml version="1.0"?>
<manifest package="org.dudetronics.droidstar" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="57" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter"/>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest>

@ -0,0 +1,62 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion '28.0.3'
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
}

@ -0,0 +1,2 @@
android.useAndroidX=true
android.enableJetifier=true

Binary file not shown.

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
android/gradlew vendored

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,22 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
</array>
<array name="qt_libs">
<!-- %%INSERT_QT_LIBS%% -->
</array>
<array name="load_local_libs">
<!-- %%INSERT_LOCAL_LIBS%% -->
</array>
</resources>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1027" product-id="24597" />
<usb-device vendor-id="3405" product-id="567" />
<usb-device vendor-id="1155" product-id="22336" />
</resources>

@ -0,0 +1,191 @@
/*
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/>.
*/
package DroidStar;
import java.util.Arrays;
import java.util.List;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbDeviceConnection;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManagerTest;
public class USBSerialWrapper implements SerialInputOutputManagerTest.Listener {
public static int counter = 0;
public int id;
public Context m_context;
public int m_baudrate = 115200;
public int m_databits = 8;
public int m_stopbits = 1;
public int m_parity = 0;
public int m_flowcontrol = 0;
public int m_rts = 0;
UsbSerialPort m_port;
SerialInputOutputManagerTest usbIoManager;
public USBSerialWrapper() {
this.id = counter;
System.out.println("Created USBSerialWrapper object with id: " + this.id);
counter++;
}
public void set_baud_rate(int br)
{
m_baudrate = br;
System.out.println("Java Baudrate: " + br);
}
public void set_data_bits(int db)
{
m_databits = db;
System.out.println("Java DataBits: " + db);
}
public void set_stop_bits(int sb)
{
m_stopbits = sb;
System.out.println("Java StopBits: " + sb);
}
public void set_parity(int p)
{
m_parity = p;
System.out.println("Java Parity: " + p);
}
public void set_flow_control(int fc)
{
m_flowcontrol = fc;
System.out.println("Java FlowControl: " + fc);
}
public void set_rts(int rts)
{
m_rts = rts;
System.out.println("Java RTS: " + rts);
}
public String[] discover_devices(Context c)
{
System.out.println("Java: discover_devices()");
UsbManager manager = (UsbManager) c.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
String[] devices = new String[availableDrivers.size()];
if (availableDrivers.isEmpty()) {
System.out.println("No drivers found");
}
else {
System.out.println(availableDrivers.size() + " found");
for(int i = 0; i < availableDrivers.size(); ++i){
devices[i] = availableDrivers.get(i).getDevice().getProductName();
System.out.println("USB device getDeviceName() == " + availableDrivers.get(i).getDevice().getDeviceName());
System.out.println("USB device getProductName() == " + availableDrivers.get(i).getDevice().getProductName());
System.out.println("USB device getProductId() == " + availableDrivers.get(i).getDevice().getProductId());
System.out.println("USB device getVendorId() == " + availableDrivers.get(i).getDevice().getVendorId());
}
}
return devices;
}
public String setup_serial(Context c)
{
m_context = c;
UsbManager manager = (UsbManager) m_context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
if (availableDrivers.isEmpty()) {
System.out.println("No drivers found");
return "returning...";
}
else {
System.out.println(availableDrivers.size() + " found");
for(int i = 0; i < availableDrivers.size(); ++i){
System.out.println("USB device getDeviceName() == " + availableDrivers.get(i).getDevice().getDeviceName());
System.out.println("USB device getProductName() == " + availableDrivers.get(i).getDevice().getProductName());
System.out.println("USB device getProductId() == " + availableDrivers.get(i).getDevice().getProductId());
System.out.println("USB device getVendorId() == " + availableDrivers.get(i).getDevice().getVendorId());
}
}
UsbSerialDriver driver = availableDrivers.get(0);
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
if (connection == null) {
// add UsbManager.requestPermission(driver.getDevice(), ..) handling here
return "No connection, need permission?";
}
m_port = driver.getPorts().get(0); // Most devices have just one port (port 0)
try {
m_port.open(connection);
m_port.setParameters(m_baudrate, m_databits, m_stopbits, m_parity);
m_port.setRTS(true);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
}
System.out.println("Created UsbManager");
usbIoManager = new SerialInputOutputManagerTest(m_port, this);
usbIoManager.start();
return "Yipee!";
}
public void write(byte[] data)
{
try {
m_port.write(data, 0);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
}
}
public byte[] read()
{
byte[] buffer = new byte[1024];
int r = 0;
try {
r = m_port.read(buffer, 0);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
}
byte[] returnbytes = Arrays.copyOf(buffer, r);
return returnbytes;
}
@Override
public void onNewData(byte[] data) {
data_received(data);
}
@Override
public void onRunError(Exception e) {
System.out.println("Exception: " + e.getMessage());
}
private static native void data_received(byte[] data);
}

@ -0,0 +1,327 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* USB CDC/ACM serial driver implementation.
*
* @author mike wakerly (opensource@hoho.com)
* @see <a
* href="http://www.usb.org/developers/devclass_docs/usbcdc11.pdf">Universal
* Serial Bus Class Definitions for Communication Devices, v1.1</a>
*/
public class CdcAcmSerialDriver implements UsbSerialDriver {
private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
private final UsbDevice mDevice;
private final List<UsbSerialPort> mPorts;
public CdcAcmSerialDriver(UsbDevice device) {
mDevice = device;
mPorts = new ArrayList<>();
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for( int i = 0; i < device.getInterfaceCount(); i++) {
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM)
controlInterfaceCount++;
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port));
}
if(mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1));
}
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public List<UsbSerialPort> getPorts() {
return mPorts;
}
public class CdcAcmSerialPort extends CommonUsbSerialPort {
private UsbInterface mControlInterface;
private UsbInterface mDataInterface;
private UsbEndpoint mControlEndpoint;
private int mControlIndex;
private boolean mRts = false;
private boolean mDtr = false;
private static final int USB_RECIP_INTERFACE = 0x01;
private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE;
private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2
private static final int GET_LINE_CODING = 0x21;
private static final int SET_CONTROL_LINE_STATE = 0x22;
private static final int SEND_BREAK = 0x23;
public CdcAcmSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
}
@Override
public UsbSerialDriver getDriver() {
return CdcAcmSerialDriver.this;
}
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
if (mPortNumber == -1) {
Log.d(TAG,"device might be castrated ACM device, trying single interface logic");
openSingleInterface();
} else {
Log.d(TAG,"trying default interface logic");
openInterface();
}
}
private void openSingleInterface() throws IOException {
// the following code is inspired by the cdc-acm driver in the linux kernel
mControlIndex = 0;
mControlInterface = mDevice.getInterface(0);
mDataInterface = mDevice.getInterface(0);
if (!mConnection.claimInterface(mControlInterface, true)) {
throw new IOException("Could not claim shared control/data interface");
}
for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) {
UsbEndpoint ep = mControlInterface.getEndpoint(i);
if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) {
mControlEndpoint = ep;
} else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
mReadEndpoint = ep;
} else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
mWriteEndpoint = ep;
}
}
if (mControlEndpoint == null) {
throw new IOException("No control endpoint");
}
}
private void openInterface() throws IOException {
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
mControlInterface = null;
mDataInterface = null;
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) {
if(controlInterfaceCount == mPortNumber) {
mControlIndex = i;
mControlInterface = usbInterface;
}
controlInterfaceCount++;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
if(dataInterfaceCount == mPortNumber) {
mDataInterface = usbInterface;
}
dataInterfaceCount++;
}
}
if(mControlInterface == null) {
throw new IOException("No control interface");
}
Log.d(TAG, "Control iface=" + mControlInterface);
if (!mConnection.claimInterface(mControlInterface, true)) {
throw new IOException("Could not claim control interface");
}
mControlEndpoint = mControlInterface.getEndpoint(0);
if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) {
throw new IOException("Invalid control endpoint");
}
if(mDataInterface == null) {
throw new IOException("No data interface");
}
Log.d(TAG, "data iface=" + mDataInterface);
if (!mConnection.claimInterface(mDataInterface, true)) {
throw new IOException("Could not claim data interface");
}
for (int i = 0; i < mDataInterface.getEndpointCount(); i++) {
UsbEndpoint ep = mDataInterface.getEndpoint(i);
if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
mReadEndpoint = ep;
if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
mWriteEndpoint = ep;
}
}
private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException {
int len = mConnection.controlTransfer(
USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000);
if(len < 0) {
throw new IOException("controlTransfer failed");
}
return len;
}
@Override
protected void closeInt() {
try {
mConnection.releaseInterface(mControlInterface);
mConnection.releaseInterface(mDataInterface);
} catch(Exception ignored) {}
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
if(dataBits < DATABITS_5 || dataBits > DATABITS_8) {
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
byte stopBitsByte;
switch (stopBits) {
case STOPBITS_1: stopBitsByte = 0; break;
case STOPBITS_1_5: stopBitsByte = 1; break;
case STOPBITS_2: stopBitsByte = 2; break;
default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
}
byte parityBitesByte;
switch (parity) {
case 0: parityBitesByte = 0; break;
case 1: parityBitesByte = 1; break;
case 2: parityBitesByte = 2; break;
case 3: parityBitesByte = 3; break;
case 4: parityBitesByte = 4; break;
default: throw new IllegalArgumentException("Invalid parity: " + parity);
}
byte[] msg = {
(byte) ( baudRate & 0xff),
(byte) ((baudRate >> 8 ) & 0xff),
(byte) ((baudRate >> 16) & 0xff),
(byte) ((baudRate >> 24) & 0xff),
stopBitsByte,
parityBitesByte,
(byte) dataBits};
sendAcmControlMessage(SET_LINE_CODING, 0, msg);
}
@Override
public boolean getDTR() throws IOException {
return mDtr;
}
@Override
public void setDTR(boolean value) throws IOException {
mDtr = value;
setDtrRts();
}
@Override
public boolean getRTS() throws IOException {
return mRts;
}
@Override
public void setRTS(boolean value) throws IOException {
mRts = value;
setDtrRts();
}
private void setDtrRts() throws IOException {
int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0);
sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null);
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(mRts) set.add(ControlLine.RTS);
if(mDtr) set.add(ControlLine.DTR);
return set;
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.of(ControlLine.RTS, ControlLine.DTR);
}
@Override
public void setBreak(boolean value) throws IOException {
sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null);
}
}
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_ARDUINO,
new int[] {
UsbId.ARDUINO_UNO,
UsbId.ARDUINO_UNO_R3,
UsbId.ARDUINO_MEGA_2560,
UsbId.ARDUINO_MEGA_2560_R3,
UsbId.ARDUINO_SERIAL_ADAPTER,
UsbId.ARDUINO_SERIAL_ADAPTER_R3,
UsbId.ARDUINO_MEGA_ADK,
UsbId.ARDUINO_MEGA_ADK_R3,
UsbId.ARDUINO_LEONARDO,
UsbId.ARDUINO_MICRO,
});
supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH,
new int[] {
UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
});
supportedDevices.put(UsbId.VENDOR_ATMEL,
new int[] {
UsbId.ATMEL_LUFA_CDC_DEMO_APP,
});
supportedDevices.put(UsbId.VENDOR_LEAFLABS,
new int[] {
UsbId.LEAFLABS_MAPLE,
});
supportedDevices.put(UsbId.VENDOR_ARM,
new int[] {
UsbId.ARM_MBED,
});
supportedDevices.put(UsbId.VENDOR_ST,
new int[] {
UsbId.ST_CDC,
});
return supportedDevices;
}
}

@ -0,0 +1,301 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest;
import android.util.Log;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.EnumSet;
/**
* A base class shared by several driver implementations.
*
* @author mike wakerly (opensource@hoho.com)
*/
public abstract class CommonUsbSerialPort implements UsbSerialPort {
private static final String TAG = CommonUsbSerialPort.class.getSimpleName();
private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit
protected final UsbDevice mDevice;
protected final int mPortNumber;
// non-null when open()
protected UsbDeviceConnection mConnection = null;
protected UsbEndpoint mReadEndpoint;
protected UsbEndpoint mWriteEndpoint;
protected UsbRequest mUsbRequest;
protected final Object mWriteBufferLock = new Object();
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
protected byte[] mWriteBuffer;
public CommonUsbSerialPort(UsbDevice device, int portNumber) {
mDevice = device;
mPortNumber = portNumber;
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
}
@Override
public String toString() {
return String.format("<%s device_name=%s device_id=%s port_number=%s>",
getClass().getSimpleName(), mDevice.getDeviceName(),
mDevice.getDeviceId(), mPortNumber);
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public int getPortNumber() {
return mPortNumber;
}
@Override
public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; }
@Override
public UsbEndpoint getReadEndpoint() { return mReadEndpoint; }
/**
* Returns the device serial number
* @return serial number
*/
@Override
public String getSerial() {
return mConnection.getSerial();
}
/**
* Sets the size of the internal buffer used to exchange data with the USB
* stack for write operations. Most users should not need to change this.
*
* @param bufferSize the size in bytes
*/
public final void setWriteBufferSize(int bufferSize) {
synchronized (mWriteBufferLock) {
if (bufferSize == mWriteBuffer.length) {
return;
}
mWriteBuffer = new byte[bufferSize];
}
}
@Override
public void open(UsbDeviceConnection connection) throws IOException {
if (mConnection != null) {
throw new IOException("Already open");
}
if(connection == null) {
throw new IllegalArgumentException("Connection is null");
}
mConnection = connection;
try {
openInt(connection);
if (mReadEndpoint == null || mWriteEndpoint == null) {
throw new IOException("Could not get read & write endpoints");
}
mUsbRequest = new UsbRequest();
mUsbRequest.initialize(mConnection, mReadEndpoint);
} catch(Exception e) {
try {
close();
} catch(Exception ignored) {}
throw e;
}
}
protected abstract void openInt(UsbDeviceConnection connection) throws IOException;
@Override
public void close() throws IOException {
if (mConnection == null) {
throw new IOException("Already closed");
}
try {
mUsbRequest.cancel();
} catch(Exception ignored) {}
mUsbRequest = null;
try {
closeInt();
} catch(Exception ignored) {}
try {
mConnection.close();
} catch(Exception ignored) {}
mConnection = null;
}
protected abstract void closeInt();
/**
* use simple USB request supported by all devices to test if connection is still valid
*/
protected void testConnection() throws IOException {
byte[] buf = new byte[2];
int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200);
if(len < 0)
throw new IOException("USB get_status request failed");
}
@Override
public int read(final byte[] dest, final int timeout) throws IOException {
return read(dest, timeout, true);
}
protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException {
if(mConnection == null) {
throw new IOException("Connection closed");
}
if(dest.length <= 0) {
throw new IllegalArgumentException("Read buffer to small");
}
final int nread;
if (timeout != 0) {
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
// https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data
// but mConnection.requestWait(timeout) available since Android 8.0 es even worse,
// as it crashes with short timeout, e.g.
// A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test)
// /system/lib64/libusbhost.so (usb_request_wait+192)
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84)
// data loss / crashes were observed with timeout up to 200 msec
long endTime = testConnection ? MonotonicClock.millis() + timeout : 0;
int readMax = Math.min(dest.length, MAX_READ_SIZE);
nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout);
// Android error propagation is improvable:
// nread == -1 can be: timeout, connection lost, buffer to small, ???
if(nread == -1 && testConnection && MonotonicClock.millis() < endTime)
testConnection();
} else {
final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!mUsbRequest.queue(buf, dest.length)) {
throw new IOException("Queueing USB request failed");
}
final UsbRequest response = mConnection.requestWait();
if (response == null) {
throw new IOException("Waiting for USB request failed");
}
nread = buf.position();
// Android error propagation is improvable:
// response != null & nread == 0 can be: connection lost, buffer to small, ???
if(nread == 0) {
testConnection();
}
}
return Math.max(nread, 0);
}
@Override
public void write(final byte[] src, final int timeout) throws IOException {
int offset = 0;
final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout);
if(mConnection == null) {
throw new IOException("Connection closed");
}
while (offset < src.length) {
int requestTimeout;
final int requestLength;
final int actualLength;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
requestLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, requestLength);
writeBuffer = mWriteBuffer;
}
if (timeout == 0 || offset == 0) {
requestTimeout = timeout;
} else {
requestTimeout = (int)(endTime - MonotonicClock.millis());
if(requestTimeout == 0)
requestTimeout = -1;
}
if (requestTimeout < 0) {
actualLength = -2;
} else {
actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout);
}
}
Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout);
if (actualLength <= 0) {
if (timeout != 0 && MonotonicClock.millis() >= endTime) {
SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength);
ex.bytesTransferred = offset;
throw ex;
} else {
throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length);
}
}
offset += actualLength;
}
}
@Override
public boolean isOpen() {
return mConnection != null;
}
@Override
public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException;
@Override
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getRI() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public abstract EnumSet<ControlLine> getControlLines() throws IOException;
@Override
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException;
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }
}

@ -0,0 +1,335 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Cp21xxSerialDriver implements UsbSerialDriver {
private static final String TAG = Cp21xxSerialDriver.class.getSimpleName();
private final UsbDevice mDevice;
private final List<UsbSerialPort> mPorts;
public Cp21xxSerialDriver(UsbDevice device) {
mDevice = device;
mPorts = new ArrayList<>();
for( int port = 0; port < device.getInterfaceCount(); port++) {
mPorts.add(new Cp21xxSerialPort(mDevice, port));
}
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public List<UsbSerialPort> getPorts() {
return mPorts;
}
public class Cp21xxSerialPort extends CommonUsbSerialPort {
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
/*
* Configuration Request Types
*/
private static final int REQTYPE_HOST_TO_DEVICE = 0x41;
private static final int REQTYPE_DEVICE_TO_HOST = 0xc1;
/*
* Configuration Request Codes
*/
private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00;
private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03;
private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05;
private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07;
private static final int SILABSER_SET_BAUDRATE = 0x1E;
private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12;
private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08;
private static final int FLUSH_READ_CODE = 0x0a;
private static final int FLUSH_WRITE_CODE = 0x05;
/*
* SILABSER_IFC_ENABLE_REQUEST_CODE
*/
private static final int UART_ENABLE = 0x0001;
private static final int UART_DISABLE = 0x0000;
/*
* SILABSER_SET_MHS_REQUEST_CODE
*/
private static final int DTR_ENABLE = 0x101;
private static final int DTR_DISABLE = 0x100;
private static final int RTS_ENABLE = 0x202;
private static final int RTS_DISABLE = 0x200;
/*
* SILABSER_GET_MDMSTS_REQUEST_CODE
*/
private static final int STATUS_CTS = 0x10;
private static final int STATUS_DSR = 0x20;
private static final int STATUS_RI = 0x40;
private static final int STATUS_CD = 0x80;
private boolean dtr = false;
private boolean rts = false;
// second port of Cp2105 has limited baudRate, dataBits, stopBits, parity
// unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored
private boolean mIsRestrictedPort;
public Cp21xxSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
}
@Override
public UsbSerialDriver getDriver() {
return Cp21xxSerialDriver.this;
}
private void setConfigSingle(int request, int value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value,
mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result);
}
}
private byte getStatus() throws IOException {
byte[] buffer = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 1) {
throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result);
}
return buffer[0];
}
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
if(mPortNumber >= mDevice.getInterfaceCount()) {
throw new IOException("Unknown port number");
}
UsbInterface dataIface = mDevice.getInterface(mPortNumber);
if (!mConnection.claimInterface(dataIface, true)) {
throw new IOException("Could not claim interface " + mPortNumber);
}
for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mReadEndpoint = ep;
} else {
mWriteEndpoint = ep;
}
}
}
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE);
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
}
@Override
protected void closeInt() {
try {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
} catch (Exception ignored) {}
try {
mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
} catch(Exception ignored) {}
}
private void setBaudRate(int baudRate) throws IOException {
byte[] data = new byte[] {
(byte) ( baudRate & 0xff),
(byte) ((baudRate >> 8 ) & 0xff),
(byte) ((baudRate >> 16) & 0xff),
(byte) ((baudRate >> 24) & 0xff)
};
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE,
0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS);
if (ret < 0) {
throw new IOException("Error setting baud rate");
}
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudRate(baudRate);
int configDataBits = 0;
switch (dataBits) {
case DATABITS_5:
if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0500;
break;
case DATABITS_6:
if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0600;
break;
case DATABITS_7:
if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0700;
break;
case DATABITS_8:
configDataBits |= 0x0800;
break;
default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
switch (parity) {
case 0:
break;
case 1:
configDataBits |= 0x0010;
break;
case 2:
configDataBits |= 0x0020;
break;
case 3:
if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported parity: mark");
configDataBits |= 0x0030;
break;
case 4:
if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported parity: space");
configDataBits |= 0x0040;
break;
default:
throw new IllegalArgumentException("Invalid parity: " + parity);
}
switch (stopBits) {
case STOPBITS_1:
break;
case STOPBITS_1_5:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2:
if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported stop bits: 2");
configDataBits |= 2;
break;
default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
}
setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits);
}
@Override
public boolean getCD() throws IOException {
return (getStatus() & STATUS_CD) != 0;
}
@Override
public boolean getCTS() throws IOException {
return (getStatus() & STATUS_CTS) != 0;
}
@Override
public boolean getDSR() throws IOException {
return (getStatus() & STATUS_DSR) != 0;
}
@Override
public boolean getDTR() throws IOException {
return dtr;
}
@Override
public void setDTR(boolean value) throws IOException {
dtr = value;
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE);
}
@Override
public boolean getRI() throws IOException {
return (getStatus() & STATUS_RI) != 0;
}
@Override
public boolean getRTS() throws IOException {
return rts;
}
@Override
public void setRTS(boolean value) throws IOException {
rts = value;
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE);
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
byte status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS);
if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR);
if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
return set;
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
@Override
// note: only working on some devices, on other devices ignored w/o error
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0)
| (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0);
if (value != 0) {
setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value);
}
}
@Override
public void setBreak(boolean value) throws IOException {
setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0);
}
}
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_SILABS,
new int[] {
UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109
UsbId.SILABS_CP2105,
UsbId.SILABS_CP2108,
});
return supportedDevices;
}
}

@ -0,0 +1,431 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
* Copyright 2020 kai morich <mail@kai-morich.de>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.util.Log;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/*
* driver is implemented from various information scattered over FTDI documentation
*
* baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf
* control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf
* device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice
*
*/
public class FtdiSerialDriver implements UsbSerialDriver {
private static final String TAG = FtdiSerialPort.class.getSimpleName();
private final UsbDevice mDevice;
private final List<UsbSerialPort> mPorts;
public FtdiSerialDriver(UsbDevice device) {
mDevice = device;
mPorts = new ArrayList<>();
for( int port = 0; port < device.getInterfaceCount(); port++) {
mPorts.add(new FtdiSerialPort(mDevice, port));
}
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public List<UsbSerialPort> getPorts() {
return mPorts;
}
public class FtdiSerialPort extends CommonUsbSerialPort {
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS
private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
private static final int RESET_REQUEST = 0;
private static final int MODEM_CONTROL_REQUEST = 1;
private static final int SET_BAUD_RATE_REQUEST = 3;
private static final int SET_DATA_REQUEST = 4;
private static final int GET_MODEM_STATUS_REQUEST = 5;
private static final int SET_LATENCY_TIMER_REQUEST = 9;
private static final int GET_LATENCY_TIMER_REQUEST = 10;
private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101;
private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100;
private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202;
private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200;
private static final int MODEM_STATUS_CTS = 0x10;
private static final int MODEM_STATUS_DSR = 0x20;
private static final int MODEM_STATUS_RI = 0x40;
private static final int MODEM_STATUS_CD = 0x80;
private static final int RESET_ALL = 0;
private static final int RESET_PURGE_RX = 1;
private static final int RESET_PURGE_TX = 2;
private boolean baudRateWithPort = false;
private boolean dtr = false;
private boolean rts = false;
private int breakConfig = 0;
public FtdiSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
}
@Override
public UsbSerialDriver getDriver() {
return FtdiSerialDriver.this;
}
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
throw new IOException("Could not claim interface " + mPortNumber);
}
if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) {
throw new IOException("Not enough endpoints");
}
mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1);
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Reset failed: result=" + result);
}
result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
(dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) |
(rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE),
mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Init RTS,DTR failed: result=" + result);
}
// mDevice.getVersion() would require API 23
byte[] rawDescriptors = connection.getRawDescriptors();
if(rawDescriptors == null || rawDescriptors.length < 14) {
throw new IOException("Could not get device descriptors");
}
int deviceType = rawDescriptors[13];
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices
|| mDevice.getInterfaceCount() > 1; // FT2232C
}
@Override
protected void closeInt() {
try {
mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
} catch(Exception ignored) {}
}
@Override
public int read(final byte[] dest, final int timeout) throws IOException {
if(dest.length <= READ_HEADER_LENGTH) {
throw new IllegalArgumentException("Read buffer to small");
// could allocate larger buffer, including space for 2 header bytes, but this would
// result in buffers not being 64 byte aligned any more, causing data loss at continuous
// data transfer at high baud rates when buffers are fully filled.
}
int nread;
if (timeout != 0) {
long endTime = MonotonicClock.millis() + timeout;
do {
nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false);
} while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime);
if(nread <= 0 && MonotonicClock.millis() < endTime)
testConnection();
} else {
do {
nread = super.read(dest, timeout, false);
} while (nread == READ_HEADER_LENGTH);
}
return readFilter(dest, nread);
}
protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
int destPos = 0;
for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) {
int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH);
if (length < 0)
throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes");
System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length);
destPos += length;
}
//Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos);
return destPos;
}
private void setBaudrate(int baudRate) throws IOException {
int divisor, subdivisor, effectiveBaudRate;
if (baudRate > 3500000) {
throw new UnsupportedOperationException("Baud rate to high");
} else if(baudRate >= 2500000) {
divisor = 0;
subdivisor = 0;
effectiveBaudRate = 3000000;
} else if(baudRate >= 1750000) {
divisor = 1;
subdivisor = 0;
effectiveBaudRate = 2000000;
} else {
divisor = (24000000 << 1) / baudRate;
divisor = (divisor + 1) >> 1; // round
subdivisor = divisor & 0x07;
divisor >>= 3;
if (divisor > 0x3fff) // exceeds bit 13 at 183 baud
throw new UnsupportedOperationException("Baud rate to low");
effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor);
effectiveBaudRate = (effectiveBaudRate +1) >> 1;
}
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
if(baudRateError >= 0.031) // can happen only > 1.5Mbaud
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
int value = divisor;
int index = 0;
switch(subdivisor) {
case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0
case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5
case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25
case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125
case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375
case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625
case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75
case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875
}
if(baudRateWithPort) {
index <<= 8;
index |= mPortNumber+1;
}
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d",
baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor));
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST,
value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting baudrate failed: result=" + result);
}
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudrate(baudRate);
int config = 0;
switch (dataBits) {
case DATABITS_5:
case DATABITS_6:
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
case DATABITS_7:
case DATABITS_8:
config |= dataBits;
break;
default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
switch (parity) {
case 0:
break;
case 1:
config |= 0x100;
break;
case 2:
config |= 0x200;
break;
case 3:
config |= 0x300;
break;
case 4:
config |= 0x400;
break;
default:
throw new IllegalArgumentException("Invalid parity: " + parity);
}
switch (stopBits) {
case STOPBITS_1:
break;
case STOPBITS_1_5:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2:
config |= 0x1000;
break;
default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
}
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting parameters failed: result=" + result);
}
breakConfig = config;
}
private int getStatus() throws IOException {
byte[] data = new byte[2];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST,
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 2) {
throw new IOException("Get modem status failed: result=" + result);
}
return data[0];
}
@Override
public boolean getCD() throws IOException {
return (getStatus() & MODEM_STATUS_CD) != 0;
}
@Override
public boolean getCTS() throws IOException {
return (getStatus() & MODEM_STATUS_CTS) != 0;
}
@Override
public boolean getDSR() throws IOException {
return (getStatus() & MODEM_STATUS_DSR) != 0;
}
@Override
public boolean getDTR() throws IOException {
return dtr;
}
@Override
public void setDTR(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Set DTR failed: result=" + result);
}
dtr = value;
}
@Override
public boolean getRI() throws IOException {
return (getStatus() & MODEM_STATUS_RI) != 0;
}
@Override
public boolean getRTS() throws IOException {
return rts;
}
@Override
public void setRTS(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Set DTR failed: result=" + result);
}
rts = value;
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS);
if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR);
if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI);
return set;
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Purge write buffer failed: result=" + result);
}
}
if (purgeReadBuffers) {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Purge read buffer failed: result=" + result);
}
}
}
@Override
public void setBreak(boolean value) throws IOException {
int config = breakConfig;
if(value) config |= 0x4000;
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting BREAK failed: result=" + result);
}
}
public void setLatencyTimer(int latencyTime) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Set latency timer failed: result=" + result);
}
}
public int getLatencyTimer() throws IOException {
byte[] data = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST,
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 1) {
throw new IOException("Get latency timer failed: result=" + result);
}
return data[0];
}
}
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_FTDI,
new int[] {
UsbId.FTDI_FT232R,
UsbId.FTDI_FT232H,
UsbId.FTDI_FT2232H,
UsbId.FTDI_FT4232H,
UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD
});
return supportedDevices;
}
}

@ -0,0 +1,87 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.util.Pair;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Maps (vendor id, product id) pairs to the corresponding serial driver.
*
* @author mike wakerly (opensource@hoho.com)
*/
public class ProbeTable {
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable =
new LinkedHashMap<>();
/**
* Adds or updates a (vendor, product) pair in the table.
*
* @param vendorId the USB vendor id
* @param productId the USB product id
* @param driverClass the driver class responsible for this pair
* @return {@code this}, for chaining
*/
public ProbeTable addProduct(int vendorId, int productId,
Class<? extends UsbSerialDriver> driverClass) {
mProbeTable.put(Pair.create(vendorId, productId), driverClass);
return this;
}
/**
* Internal method to add all supported products from
* {@code getSupportedProducts} static method.
*
* @param driverClass
* @return
*/
@SuppressWarnings("unchecked")
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) {
final Method method;
try {
method = driverClass.getMethod("getSupportedDevices");
} catch (SecurityException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
final Map<Integer, int[]> devices;
try {
devices = (Map<Integer, int[]>) method.invoke(null);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
for (Map.Entry<Integer, int[]> entry : devices.entrySet()) {
final int vendorId = entry.getKey();
for (int productId : entry.getValue()) {
addProduct(vendorId, productId, driverClass);
}
}
return this;
}
/**
* Returns the driver for the given (vendor, product) pair, or {@code null}
* if no match.
*
* @param vendorId the USB vendor id
* @param productId the USB product id
* @return the driver class matching this pair, or {@code null}
*/
public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) {
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId);
return mProbeTable.get(pair);
}
}

@ -0,0 +1,15 @@
package com.hoho.android.usbserial.driver;
import java.io.InterruptedIOException;
/**
* Signals that a timeout has occurred on serial write.
* Similar to SocketTimeoutException.
*
* {@see InterruptedIOException#bytesTransferred} may contain bytes transferred
*/
public class SerialTimeoutException extends InterruptedIOException {
public SerialTimeoutException(String s) {
super(s);
}
}

@ -0,0 +1,76 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
/**
* Registry of USB vendor/product ID constants.
*
* Culled from various sources; see
* <a href="http://www.linux-usb.org/usb.ids">usb.ids</a> for one listing.
*
* @author mike wakerly (opensource@hoho.com)
*/
public final class UsbId {
public static final int VENDOR_FTDI = 0x0403;
public static final int FTDI_FT232R = 0x6001;
public static final int FTDI_FT2232H = 0x6010;
public static final int FTDI_FT4232H = 0x6011;
public static final int FTDI_FT232H = 0x6014;
public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD
public static final int VENDOR_ATMEL = 0x03EB;
public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044;
public static final int VENDOR_ARDUINO = 0x2341;
public static final int ARDUINO_UNO = 0x0001;
public static final int ARDUINO_MEGA_2560 = 0x0010;
public static final int ARDUINO_SERIAL_ADAPTER = 0x003b;
public static final int ARDUINO_MEGA_ADK = 0x003f;
public static final int ARDUINO_MEGA_2560_R3 = 0x0042;
public static final int ARDUINO_UNO_R3 = 0x0043;
public static final int ARDUINO_MEGA_ADK_R3 = 0x0044;
public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044;
public static final int ARDUINO_LEONARDO = 0x8036;
public static final int ARDUINO_MICRO = 0x8037;
public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0;
public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483;
public static final int VENDOR_LEAFLABS = 0x1eaf;
public static final int LEAFLABS_MAPLE = 0x0004;
public static final int VENDOR_SILABS = 0x10c4;
public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109
public static final int SILABS_CP2105 = 0xea70;
public static final int SILABS_CP2108 = 0xea71;
public static final int VENDOR_PROLIFIC = 0x067b;
public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX
public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN
public static final int PROLIFIC_PL2303GB = 0x23b3; // "
public static final int PROLIFIC_PL2303GT = 0x23c3; // "
public static final int PROLIFIC_PL2303GL = 0x23d3; // "
public static final int PROLIFIC_PL2303GE = 0x23e3; // "
public static final int PROLIFIC_PL2303GS = 0x23f3; // "
public static final int VENDOR_QINHENG = 0x1a86;
public static final int QINHENG_CH340 = 0x7523;
public static final int QINHENG_CH341A = 0x5523;
// at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids
public static final int VENDOR_ARM = 0x0d28;
public static final int ARM_MBED = 0x0204;
public static final int VENDOR_ST = 0x0483;
public static final int ST_CDC = 0x5740;
private UsbId() {
throw new IllegalAccessError("Non-instantiable class");
}
}

@ -0,0 +1,33 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import java.util.List;
/**
*
* @author mike wakerly (opensource@hoho.com)
*/
public interface UsbSerialDriver {
/**
* Returns the raw {@link UsbDevice} backing this port.
*
* @return the device
*/
UsbDevice getDevice();
/**
* Returns all available ports for this device. This list must have at least
* one entry.
*
* @return the ports
*/
List<UsbSerialPort> getPorts();
}

@ -0,0 +1,261 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager;
//import androidx.annotation.IntDef;
import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.EnumSet;
/**
* Interface for a single serial port.
*
* @author mike wakerly (opensource@hoho.com)
*/
public interface UsbSerialPort extends Closeable {
/** 5 data bits. */
int DATABITS_5 = 5;
/** 6 data bits. */
int DATABITS_6 = 6;
/** 7 data bits. */
int DATABITS_7 = 7;
/** 8 data bits. */
int DATABITS_8 = 8;
/** Values for setParameters(..., parity) */
//@Retention(RetentionPolicy.SOURCE)
//@IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE})
//@interface Parity {}
/** No parity. */
int PARITY_NONE = 0;
/** Odd parity. */
int PARITY_ODD = 1;
/** Even parity. */
int PARITY_EVEN = 2;
/** Mark parity. */
int PARITY_MARK = 3;
/** Space parity. */
int PARITY_SPACE = 4;
/** 1 stop bit. */
int STOPBITS_1 = 1;
/** 1.5 stop bits. */
int STOPBITS_1_5 = 3;
/** 2 stop bits. */
int STOPBITS_2 = 2;
/** Values for get[Supported]ControlLines() */
enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }
/**
* Returns the driver used by this port.
*/
UsbSerialDriver getDriver();
/**
* Returns the currently-bound USB device.
*/
UsbDevice getDevice();
/**
* Port number within driver.
*/
int getPortNumber();
/**
* Returns the write endpoint.
* @return write endpoint
*/
UsbEndpoint getWriteEndpoint();
/**
* Returns the read endpoint.
* @return read endpoint
*/
UsbEndpoint getReadEndpoint();
/**
* The serial number of the underlying UsbDeviceConnection, or {@code null}.
*
* @return value from {@link UsbDeviceConnection#getSerial()}
* @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted
*/
String getSerial();
/**
* Opens and initializes the port. Upon success, caller must ensure that
* {@link #close()} is eventually called.
*
* @param connection an open device connection, acquired with
* {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)}
* @throws IOException on error opening or initializing the port.
*/
void open(UsbDeviceConnection connection) throws IOException;
/**
* Closes the port and {@link UsbDeviceConnection}
*
* @throws IOException on error closing the port.
*/
void close() throws IOException;
/**
* Reads as many bytes as possible into the destination buffer.
*
* @param dest the destination byte buffer
* @param timeout the timeout for reading in milliseconds, 0 is infinite
* @return the actual number of bytes read
* @throws IOException if an error occurred during reading
*/
int read(final byte[] dest, final int timeout) throws IOException;
/**
* Writes as many bytes as possible from the source buffer.
*
* @param src the source byte buffer
* @param timeout the timeout for writing in milliseconds, 0 is infinite
* @throws SerialTimeoutException if timeout reached before sending all data.
* ex.bytesTransferred may contain bytes transferred
* @throws IOException if an error occurred during writing
*/
void write(final byte[] src, final int timeout) throws IOException;
/**
* Sets various serial port parameters.
*
* @param baudRate baud rate as an integer, for example {@code 115200}.
* @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6},
* {@link #DATABITS_7}, or {@link #DATABITS_8}.
* @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}.
* @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD},
* {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}.
* @throws IOException on error setting the port parameters
* @throws UnsupportedOperationException if values are not supported by a specific device
*/
void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException;
/**
* Gets the CD (Carrier Detect) bit from the underlying UART.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getCD() throws IOException;
/**
* Gets the CTS (Clear To Send) bit from the underlying UART.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getCTS() throws IOException;
/**
* Gets the DSR (Data Set Ready) bit from the underlying UART.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getDSR() throws IOException;
/**
* Gets the DTR (Data Terminal Ready) bit from the underlying UART.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getDTR() throws IOException;
/**
* Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported.
*
* @param value the value to set
* @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/
void setDTR(boolean value) throws IOException;
/**
* Gets the RI (Ring Indicator) bit from the underlying UART.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getRI() throws IOException;
/**
* Gets the RTS (Request To Send) bit from the underlying UART.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getRTS() throws IOException;
/**
* Sets the RTS (Request To Send) bit on the underlying UART, if supported.
*
* @param value the value to set
* @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/
void setRTS(boolean value) throws IOException;
/**
* Gets all control line values from the underlying UART, if supported.
* Requires less USB calls than calling getRTS() + ... + getRI() individually.
*
* @return EnumSet.contains(...) is {@code true} if set, else {@code false}
* @throws IOException if an error occurred during reading
*/
EnumSet<ControlLine> getControlLines() throws IOException;
/**
* Gets all control line supported flags.
*
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
* @throws IOException if an error occurred during reading
*/
EnumSet<ControlLine> getSupportedControlLines() throws IOException;
/**
* Purge non-transmitted output data and / or non-read input data.
*
* @param purgeWriteBuffers {@code true} to discard non-transmitted output data
* @param purgeReadBuffers {@code true} to discard non-read input data
* @throws IOException if an error occurred during flush
* @throws UnsupportedOperationException if not supported
*/
void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException;
/**
* send BREAK condition.
*
* @param value set/reset
*/
void setBreak(boolean value) throws IOException;
/**
* Returns the current state of the connection.
*/
boolean isOpen();
}

@ -0,0 +1,92 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author mike wakerly (opensource@hoho.com)
*/
public class UsbSerialProber {
private final ProbeTable mProbeTable;
public UsbSerialProber(ProbeTable probeTable) {
mProbeTable = probeTable;
}
public static UsbSerialProber getDefaultProber() {
return new UsbSerialProber(getDefaultProbeTable());
}
public static ProbeTable getDefaultProbeTable() {
final ProbeTable probeTable = new ProbeTable();
probeTable.addDriver(CdcAcmSerialDriver.class);
probeTable.addDriver(Cp21xxSerialDriver.class);
probeTable.addDriver(FtdiSerialDriver.class);
//probeTable.addDriver(ProlificSerialDriver.class);
//probeTable.addDriver(Ch34xSerialDriver.class);
return probeTable;
}
/**
* Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers}
* from the currently-attached {@link UsbDevice} hierarchy. This method does
* not require permission from the Android USB system, since it does not
* open any of the devices.
*
* @param usbManager usb manager
* @return a list, possibly empty, of all compatible drivers
*/
public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) {
final List<UsbSerialDriver> result = new ArrayList<>();
for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
final UsbSerialDriver driver = probeDevice(usbDevice);
if (driver != null) {
result.add(driver);
}
}
return result;
}
/**
* Probes a single device for a compatible driver.
*
* @param usbDevice the usb device to probe
* @return a new {@link UsbSerialDriver} compatible with this device, or
* {@code null} if none available.
*/
public UsbSerialDriver probeDevice(final UsbDevice usbDevice) {
final int vendorId = usbDevice.getVendorId();
final int productId = usbDevice.getProductId();
final Class<? extends UsbSerialDriver> driverClass =
mProbeTable.findDriver(vendorId, productId);
if (driverClass != null) {
final UsbSerialDriver driver;
try {
final Constructor<? extends UsbSerialDriver> ctor =
driverClass.getConstructor(UsbDevice.class);
driver = ctor.newInstance(usbDevice);
} catch (NoSuchMethodException | IllegalArgumentException | InstantiationException |
IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
return driver;
}
return null;
}
}

@ -0,0 +1,14 @@
package com.hoho.android.usbserial.util;
public final class MonotonicClock {
private static final long NS_PER_MS = 1_000_000;
private MonotonicClock() {
}
public static long millis() {
return System.nanoTime() / NS_PER_MS;
}
}

@ -0,0 +1,254 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.util;
import android.os.Process;
import android.util.Log;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Utility class which services a {@link UsbSerialPort} in its {@link #run()} method.
*
* @author mike wakerly (opensource@hoho.com)
*/
public class SerialInputOutputManagerTest implements Runnable {
private static final String TAG = SerialInputOutputManagerTest.class.getSimpleName();
public static boolean DEBUG = false;
private static final int BUFSIZ = 8192;
/**
* default read timeout is infinite, to avoid data loss with bulkTransfer API
*/
private int mReadTimeout = 0;
private int mWriteTimeout = 0;
private final Object mReadBufferLock = new Object();
private final Object mWriteBufferLock = new Object();
private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize()
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
public enum State {
STOPPED,
RUNNING,
STOPPING
}
private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO;
private State mState = State.STOPPED; // Synchronized by 'this'
private Listener mListener; // Synchronized by 'this'
private final UsbSerialPort mSerialPort;
public interface Listener {
/**
* Called when new incoming data is available.
*/
void onNewData(byte[] data);
/**
* Called when {@link SerialInputOutputManager#run()} aborts due to an error.
*/
void onRunError(Exception e);
}
public SerialInputOutputManagerTest(UsbSerialPort serialPort) {
mSerialPort = serialPort;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
}
public SerialInputOutputManagerTest(UsbSerialPort serialPort, Listener listener) {
mSerialPort = serialPort;
mListener = listener;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
}
public synchronized void setListener(Listener listener) {
mListener = listener;
}
public synchronized Listener getListener() {
return mListener;
}
/**
* setThreadPriority. By default a higher priority than UI thread is used to prevent data loss
*
* @param threadPriority see {@link Process#setThreadPriority(int)}
* */
public void setThreadPriority(int threadPriority) {
if (mState != State.STOPPED)
throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started");
mThreadPriority = threadPriority;
}
/**
* read/write timeout
*/
public void setReadTimeout(int timeout) {
// when set if already running, read already blocks and the new value will not become effective now
if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED)
throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started");
mReadTimeout = timeout;
}
public int getReadTimeout() {
return mReadTimeout;
}
public void setWriteTimeout(int timeout) {
mWriteTimeout = timeout;
}
public int getWriteTimeout() {
return mWriteTimeout;
}
/**
* read/write buffer size
*/
public void setReadBufferSize(int bufferSize) {
if (getReadBufferSize() == bufferSize)
return;
synchronized (mReadBufferLock) {
mReadBuffer = ByteBuffer.allocate(bufferSize);
}
}
public int getReadBufferSize() {
return mReadBuffer.capacity();
}
public void setWriteBufferSize(int bufferSize) {
if(getWriteBufferSize() == bufferSize)
return;
synchronized (mWriteBufferLock) {
ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize);
if(mWriteBuffer.position() > 0)
newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position());
mWriteBuffer = newWriteBuffer;
}
}
public int getWriteBufferSize() {
return mWriteBuffer.capacity();
}
/**
* when using writeAsync, it is recommended to use readTimeout != 0,
* else the write will be delayed until read data is available
*/
public void writeAsync(byte[] data) {
synchronized (mWriteBufferLock) {
mWriteBuffer.put(data);
}
}
/**
* start SerialInputOutputManager in separate thread
*/
public void start() {
if(mState != State.STOPPED)
throw new IllegalStateException("already started");
new Thread(this, this.getClass().getSimpleName()).start();
}
/**
* stop SerialInputOutputManager thread
*
* when using readTimeout == 0 (default), additionally use usbSerialPort.close() to
* interrupt blocking read
*/
public synchronized void stop() {
if (getState() == State.RUNNING) {
Log.i(TAG, "Stop requested");
mState = State.STOPPING;
}
}
public synchronized State getState() {
return mState;
}
/**
* Continuously services the read and write buffers until {@link #stop()} is
* called, or until a driver exception is raised.
*/
@Override
public void run() {
synchronized (this) {
if (getState() != State.STOPPED) {
throw new IllegalStateException("Already running");
}
mState = State.RUNNING;
}
Log.i(TAG, "Running ...");
try {
if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT)
Process.setThreadPriority(mThreadPriority);
while (true) {
if (getState() != State.RUNNING) {
Log.i(TAG, "Stopping mState=" + getState());
break;
}
step();
}
} catch (Exception e) {
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
final Listener listener = getListener();
if (listener != null) {
listener.onRunError(e);
}
} finally {
synchronized (this) {
mState = State.STOPPED;
Log.i(TAG, "Stopped");
}
}
}
private void step() throws IOException {
// Handle incoming data.
byte[] buffer;
synchronized (mReadBufferLock) {
buffer = mReadBuffer.array();
}
int len = mSerialPort.read(buffer, mReadTimeout);
if (len > 0) {
if (DEBUG) Log.d(TAG, "Read data len=" + len);
final Listener listener = getListener();
if (listener != null) {
final byte[] data = new byte[len];
System.arraycopy(buffer, 0, data, 0, len);
listener.onNewData(data);
}
}
// Handle outgoing data.
buffer = null;
synchronized (mWriteBufferLock) {
len = mWriteBuffer.position();
if (len > 0) {
buffer = new byte[len];
mWriteBuffer.rewind();
mWriteBuffer.get(buffer, 0, len);
mWriteBuffer.clear();
}
}
if (buffer != null) {
if (DEBUG) {
Log.d(TAG, "Writing data len=" + len);
}
mSerialPort.write(buffer, mWriteTimeout);
}
}
}

@ -0,0 +1,132 @@
/*
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 "androidserialport.h"
#ifdef Q_OS_ANDROID
AndroidSerialPort::AndroidSerialPort(QObject * parent)
{
if(QAndroidJniObject::isClassAvailable("com.hoho.android.usbserial.driver/UsbSerialDriver")) {
qDebug() << "com.hoho.android.usbserial.driver/UsbSerialDriver available";
serialJavaObject = QAndroidJniObject("DroidStar/USBSerialWrapper");
QAndroidJniEnvironment env;
JNINativeMethod methods[] = { {"data_received", "([B)V", reinterpret_cast<void*>(java_data_received)} };
jclass objectClass = env->GetObjectClass(serialJavaObject.object<jobject>());
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass);
}
else {
qDebug() << "com.hoho.android.usbserial.driver/UsbSerialDriver not available";
}
}
QStringList AndroidSerialPort::discover_devices()
{
QAndroidJniEnvironment env;
QStringList l;
l.clear();
qDebug() << "AndroidSerialPort::discover_devices()";
QAndroidJniObject d = serialJavaObject.callObjectMethod("discover_devices", "(Landroid/content/Context;)[Ljava/lang/String;", QtAndroid::androidContext().object());
jobjectArray devices = d.object<jobjectArray>();
int size = env->GetArrayLength(devices);
for (int i=0; i < size; ++i) {
jstring s = (jstring) env->GetObjectArrayElement(devices, i);
l.append(env->GetStringUTFChars(s, JNI_FALSE));
}
return l;
}
int AndroidSerialPort::open(int p)
{
qDebug() << serialJavaObject.callObjectMethod("setup_serial", "(Landroid/content/Context;)Ljava/lang/String;", QtAndroid::androidContext().object()).toString();
return 1;
}
int AndroidSerialPort::write(char *data, int s)
{
QAndroidJniEnvironment env;
jbyteArray buffer = env->NewByteArray(s);
env->SetByteArrayRegion(buffer, 0, s, (jbyte *)data);
serialJavaObject.callMethod<void>("write", "([B)V", buffer);
return 0;
}
int AndroidSerialPort::write(QByteArray data)
{
QAndroidJniEnvironment env;
jbyteArray buffer = env->NewByteArray(data.size());
env->SetByteArrayRegion(buffer, 0, data.size(), (jbyte *)data.data());
serialJavaObject.callMethod<void>("write", "([B)V", buffer);
return 0;
}
void AndroidSerialPort::setPortName(QString s)
{
qDebug() << "setPortName() == " << s;
}
void AndroidSerialPort::setBaudRate(int br)
{
serialJavaObject.callMethod<void>("set_baud_rate", "(I)V", br);
}
void AndroidSerialPort::setDataBits(int db)
{
serialJavaObject.callMethod<void>("set_data_bits", "(I)V", db);
}
void AndroidSerialPort::setStopBits(int sb)
{
serialJavaObject.callMethod<void>("set_stop_bits", "(I)V", sb);
}
void AndroidSerialPort::setParity(int p)
{
serialJavaObject.callMethod<void>("set_parity", "(I)V", p);
}
void AndroidSerialPort::setFlowControl(int fc)
{
serialJavaObject.callMethod<void>("set_flow_control", "(I)V", fc);
}
void AndroidSerialPort::setRequestToSend(int rts)
{
serialJavaObject.callMethod<void>("set_rts", "(I)V", rts);
}
QByteArray AndroidSerialPort::readAll()
{
QByteArray r;
r.replace(0, m_received.size(), m_received);
m_received.clear();
return r;
}
void AndroidSerialPort::java_data_received(JNIEnv *env, jobject t, jbyteArray data)
{
QByteArray r;
jboolean copy;
jsize s = env->GetArrayLength(data);
jbyte *p_data = env->GetByteArrayElements(data, &copy);
r.append((char *)p_data, s);
emit AndroidSerialPort::GetInstance().data_received(r);
}
#endif

@ -0,0 +1,58 @@
/*
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/>.
*/
#ifndef ANDROIDSERIALPORT_H
#define ANDROIDSERIALPORT_H
#include <QObject>
#ifdef Q_OS_ANDROID
#include <QtAndroidExtras>
class AndroidSerialPort : public QObject
{
Q_OBJECT
public:
static AndroidSerialPort & GetInstance()
{
static AndroidSerialPort instance;
return instance;
}
QByteArray readAll();
QStringList discover_devices();
int open(int);
void close(){}
void setPortName(QString);
void setBaudRate(int);
void setDataBits(int);
void setStopBits(int);
void setParity(int);
void setFlowControl(int);
void setRequestToSend(int);
int write(QByteArray);
int write(char *, int);
signals:
void readyRead();
void data_received(QByteArray);
private:
explicit AndroidSerialPort(QObject * parent = nullptr);
static void java_data_received(JNIEnv *env, jobject t, jbyteArray data);
QAndroidJniObject serialJavaObject;
QByteArray m_received;
};
#endif
#endif // ANDROIDSERIALPORT_H

@ -0,0 +1,398 @@
/*
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 "audioengine.h"
#include <QDebug>
#include <cmath>
#ifdef Q_OS_MACOS
#define MACHAK 1
#else
#define MACHAK 0
#endif
//AudioEngine::AudioEngine(QObject *parent) : QObject(parent)
AudioEngine::AudioEngine(QString in, QString out) :
m_outputdevice(out),
m_inputdevice(in),
m_out(nullptr),
m_in(nullptr),
m_srm(1)
{
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
memset(m_aout_max_buf, 0, sizeof(float) * 200);
m_aout_max_buf_p = m_aout_max_buf;
m_aout_max_buf_idx = 0;
m_aout_gain = 100;
m_volume = 1.0f;
}
AudioEngine::~AudioEngine()
{
//m_indev->disconnect();
//m_in->stop();
//m_outdev->disconnect();
//m_out->stop();
//delete m_in;
//delete m_out;
}
QStringList AudioEngine::discover_audio_devices(uint8_t d)
{
QStringList list;
QAudio::Mode m = (d) ? QAudio::AudioOutput : QAudio::AudioInput;
QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(m);
for (QList<QAudioDeviceInfo>::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) {
//fprintf(stderr, "Playback device name = %s\n", (*it).deviceName().toStdString().c_str());fflush(stderr);
list.append((*it).deviceName());
}
return list;
}
void AudioEngine::init()
{
QAudioFormat format;
QAudioFormat tempformat;
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
m_agc = true;
QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
if(devices.size() == 0){
fprintf(stderr, "No audio playback hardware found\n");fflush(stderr);
}
else{
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
for (QList<QAudioDeviceInfo>::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) {
if(MACHAK){
qDebug() << "Playback device name = " << (*it).deviceName();
qDebug() << (*it).supportedByteOrders();
qDebug() << (*it).supportedChannelCounts();
qDebug() << (*it).supportedCodecs();
qDebug() << (*it).supportedSampleRates();
qDebug() << (*it).supportedSampleSizes();
qDebug() << (*it).supportedSampleTypes();
qDebug() << (*it).preferredFormat();
}
if((*it).deviceName() == m_outputdevice){
info = *it;
}
}
if (!info.isFormatSupported(format)) {
qWarning() << "Raw audio format not supported by backend, trying nearest format.";
tempformat = info.nearestFormat(format);
qWarning() << "Format now set to " << format.sampleRate() << ":" << format.sampleSize();
}
else{
tempformat = format;
}
fprintf(stderr, "Using playback device %s\n", info.deviceName().toStdString().c_str());fflush(stderr);
m_out = new QAudioOutput(info, tempformat, this);
m_out->setBufferSize(19200);
connect(m_out, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
//m_outdev = m_out->start();
}
devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if(devices.size() == 0){
fprintf(stderr, "No audio recording hardware found\n");fflush(stderr);
}
else{
QAudioDeviceInfo info(QAudioDeviceInfo::defaultInputDevice());
for (QList<QAudioDeviceInfo>::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) {
if(MACHAK){
qDebug() << "Capture device name = " << (*it).deviceName();
qDebug() << (*it).supportedByteOrders();
qDebug() << (*it).supportedChannelCounts();
qDebug() << (*it).supportedCodecs();
qDebug() << (*it).supportedSampleRates();
qDebug() << (*it).supportedSampleSizes();
qDebug() << (*it).supportedSampleTypes();
qDebug() << (*it).preferredFormat();
}
if((*it).deviceName() == m_inputdevice){
info = *it;
}
}
if (!info.isFormatSupported(format)) {
qWarning() << "Raw audio format not supported by backend, trying nearest format.";
tempformat = info.nearestFormat(format);
qWarning() << "Format now set to " << format.sampleRate() << ":" << format.sampleSize();
}
else{
tempformat = format;
}
int sr = 8000;
if(MACHAK){
sr = info.preferredFormat().sampleRate();
m_srm = (float)sr / 8000.0;
}
format.setSampleRate(sr);
m_in = new QAudioInput(info, format, this);
fprintf(stderr, "Capture device: %s SR: %d resample factor: %f\n", info.deviceName().toStdString().c_str(), sr, m_srm);fflush(stderr);
}
}
void AudioEngine::start_capture()
{
m_audioinq.clear();
if(m_in != nullptr){
m_indev = m_in->start();
connect(m_indev, SIGNAL(readyRead()), SLOT(input_data_received()));
}
}
void AudioEngine::stop_capture()
{
if(m_in != nullptr){
m_indev->disconnect();
m_in->stop();
}
}
void AudioEngine::start_playback()
{
//m_out->reset();
m_outdev = m_out->start();
}
void AudioEngine::stop_playback()
{
m_out->stop();
}
void AudioEngine::input_data_received()
{
QByteArray data;
qint64 len = m_in->bytesReady();
if (len > 0){
data.resize(len);
m_indev->read(data.data(), len);
/*
fprintf(stderr, "AUDIOIN: ");
for(int i = 0; i < len; ++i){
fprintf(stderr, "%02x ", (unsigned char)data.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
*/
if(MACHAK){
std::vector<int16_t> samples;
for(int i = 0; i < len; i += 2){
samples.push_back(((data.data()[i+1] << 8) & 0xff00) | (data.data()[i] & 0xff));
}
for(float i = 0; i < (float)len/2; i += m_srm){
m_audioinq.enqueue(samples[i]);
}
}
else{
for(int i = 0; i < len; i += (2 * m_srm)){
m_audioinq.enqueue(((data.data()[i+1] << 8) & 0xff00) | (data.data()[i] & 0xff));
}
}
}
}
void AudioEngine::write(int16_t *pcm, size_t s)
{
m_maxlevel = 0;
/*
fprintf(stderr, "AUDIOOUT: ");
for(int i = 0; i < s; ++i){
fprintf(stderr, "%04x ", (uint16_t)pcm[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
*/
if(m_agc){
process_audio(pcm, s);
}
m_outdev->write((const char *) pcm, sizeof(int16_t) * s);
for(uint32_t i = 0; i < s; ++i){
if(pcm[i] > m_maxlevel){
m_maxlevel = pcm[i];
}
}
}
uint16_t AudioEngine::read(int16_t *pcm, int s)
{
m_maxlevel = 0;
if(m_audioinq.size() >= s){
for(int i = 0; i < s; ++i){
pcm[i] = m_audioinq.dequeue();
if(pcm[i] > m_maxlevel){
m_maxlevel = pcm[i];
}
}
return 1;
}
else if(m_in == nullptr){
memset(pcm, 0, sizeof(int16_t) * s);
return 1;
}
else{
//fprintf(stderr, "audio frame not avail size == %d\n", m_audioinq.size());
return 0;
}
}
uint16_t AudioEngine::read(int16_t *pcm)
{
int s;
m_maxlevel = 0;
if(m_audioinq.size() >= 160){
s = 160;
}
else{
s = m_audioinq.size();
}
for(int i = 0; i < s; ++i){
pcm[i] = m_audioinq.dequeue();
if(pcm[i] > m_maxlevel){
m_maxlevel = pcm[i];
}
}
return s;
}
// process_audio() based on code from DSD https://github.com/szechyjs/dsd
void AudioEngine::process_audio(int16_t *pcm, size_t s)
{
float aout_abs, max, gainfactor, gaindelta, maxbuf;
for(size_t i = 0; i < s; ++i){
m_audio_out_temp_buf[i] = static_cast<float>(pcm[i]);
}
// detect max level
max = 0;
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
for (size_t i = 0; i < s; i++){
aout_abs = fabsf(*m_audio_out_temp_buf_p);
if (aout_abs > max){
max = aout_abs;
}
m_audio_out_temp_buf_p++;
}
*m_aout_max_buf_p = max;
m_aout_max_buf_p++;
m_aout_max_buf_idx++;
if (m_aout_max_buf_idx > 24){
m_aout_max_buf_idx = 0;
m_aout_max_buf_p = m_aout_max_buf;
}
// lookup max history
for (size_t i = 0; i < 25; i++){
maxbuf = m_aout_max_buf[i];
if (maxbuf > max){
max = maxbuf;
}
}
// determine optimal gain level
if (max > static_cast<float>(0)){
gainfactor = (static_cast<float>(30000) / max);
}
else{
gainfactor = static_cast<float>(50);
}
if (gainfactor < m_aout_gain){
m_aout_gain = gainfactor;
gaindelta = static_cast<float>(0);
}
else{
if (gainfactor > static_cast<float>(50)){
gainfactor = static_cast<float>(50);
}
gaindelta = gainfactor - m_aout_gain;
if (gaindelta > (static_cast<float>(0.05) * m_aout_gain)){
gaindelta = (static_cast<float>(0.05) * m_aout_gain);
}
}
gaindelta /= static_cast<float>(160);
// adjust output gain
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
for (size_t i = 0; i < 160; i++){
*m_audio_out_temp_buf_p = (m_aout_gain + (static_cast<float>(i) * gaindelta)) * (*m_audio_out_temp_buf_p);
m_audio_out_temp_buf_p++;
}
m_aout_gain += (static_cast<float>(s) * gaindelta);
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
for (size_t i = 0; i < s; i++){
*m_audio_out_temp_buf_p *= m_volume;
if (*m_audio_out_temp_buf_p > static_cast<float>(32760)){
*m_audio_out_temp_buf_p = static_cast<float>(32760);
}
else if (*m_audio_out_temp_buf_p < static_cast<float>(-32760)){
*m_audio_out_temp_buf_p = static_cast<float>(-32760);
}
pcm[i] = static_cast<int16_t>(*m_audio_out_temp_buf_p);
m_audio_out_temp_buf_p++;
}
}
void AudioEngine::handleStateChanged(QAudio::State newState)
{
switch (newState) {
case QAudio::ActiveState:
//qDebug() << "AudioOut state active";
break;
case QAudio::SuspendedState:
//qDebug() << "AudioOut state suspended";
break;
case QAudio::IdleState:
//qDebug() << "AudioOut state idle";
break;
case QAudio::StoppedState:
//qDebug() << "AudioOut state stopped";
break;
default:
break;
}
}

@ -0,0 +1,94 @@
/*
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/>.
*/
#ifndef AUDIOENGINE_H
#define AUDIOENGINE_H
#include <QObject>
#include <QAudio>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QAudioInput>
#include <QQueue>
#define AUDIO_OUT 1
#define AUDIO_IN 0
class AudioEngine : public QObject
{
Q_OBJECT
public:
//explicit AudioEngine(QObject *parent = nullptr);
AudioEngine(QString in, QString out);
~AudioEngine();
static QStringList discover_audio_devices(uint8_t d);
void init();
void start_capture();
void stop_capture();
void start_playback();
void stop_playback();
void write(int16_t *, size_t);
void set_output_buffer_size(uint32_t b) { m_out->setBufferSize(b); }
void set_input_buffer_size(uint32_t b) { if(m_in != nullptr) m_in->setBufferSize(b); }
void set_output_volume(qreal v){ m_out->setVolume(v); }
void set_input_volume(qreal v){ m_in->setVolume(v); }
void set_agc(bool agc) { m_agc = agc; }
bool frame_available() { return (m_audioinq.size() >= 320) ? true : false; }
uint16_t read(int16_t *, int);
uint16_t read(int16_t *);
uint16_t level() { return m_maxlevel; }
signals:
private:
QString m_outputdevice;
QString m_inputdevice;
QAudioOutput *m_out;
QAudioInput *m_in;
QIODevice *m_outdev;
QIODevice *m_indev;
QQueue<int16_t> m_audioinq;
uint16_t m_maxlevel;
bool m_agc;
float m_srm; // sample rate multiplier for macOS HACK
float m_audio_out_temp_buf[160]; //!< output of decoder
float *m_audio_out_temp_buf_p;
//float m_audio_out_float_buf[1120]; //!< output of upsampler - 1 frame of 160 samples upampled up to 7 times
//float *m_audio_out_float_buf_p;
float m_aout_max_buf[200];
float *m_aout_max_buf_p;
int m_aout_max_buf_idx;
//short m_audio_out_buf[2*48000]; //!< final result - 1s of L+R S16LE samples
//short *m_audio_out_buf_p;
//int m_audio_out_nb_samples;
//int m_audio_out_buf_size;
//int m_audio_out_idx;
//int m_audio_out_idx2;
float m_aout_gain;
float m_volume;
private slots:
void input_data_received();
void process_audio(int16_t *pcm, size_t s);
void handleStateChanged(QAudio::State newState);
};
#endif // AUDIOENGINE_H

@ -0,0 +1,369 @@
/*
* Copyright (C) 2012 by Ian Wraith
* Copyright (C) 2015 by Jonathan Naylor G4KLX
*
* 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 "cbptc19696.h"
#include "chamming.h"
//#include "cutils.h"
#include <cstdio>
#include <cassert>
#include <cstring>
CBPTC19696::CBPTC19696()
{
}
CBPTC19696::~CBPTC19696()
{
}
// The main decode function
void CBPTC19696::decode(const unsigned char* in, unsigned char* out)
{
assert(in != NULL);
assert(out != NULL);
// Get the raw binary
decodeExtractBinary(in);
// Deinterleave
decodeDeInterleave();
// Error check
decodeErrorCheck();
// Extract Data
decodeExtractData(out);
}
// The main encode function
void CBPTC19696::encode(const unsigned char* in, unsigned char* out)
{
assert(in != NULL);
assert(out != NULL);
// Extract Data
encodeExtractData(in);
// Error check
encodeErrorCheck();
// Deinterleave
encodeInterleave();
// Get the raw binary
encodeExtractBinary(out);
}
void CBPTC19696::byteToBitsBE(unsigned char byte, bool* bits)
{
assert(bits != NULL);
bits[0U] = (byte & 0x80U) == 0x80U;
bits[1U] = (byte & 0x40U) == 0x40U;
bits[2U] = (byte & 0x20U) == 0x20U;
bits[3U] = (byte & 0x10U) == 0x10U;
bits[4U] = (byte & 0x08U) == 0x08U;
bits[5U] = (byte & 0x04U) == 0x04U;
bits[6U] = (byte & 0x02U) == 0x02U;
bits[7U] = (byte & 0x01U) == 0x01U;
}
void CBPTC19696::bitsToByteBE(bool* bits, unsigned char& byte)
{
assert(bits != NULL);
byte = bits[0U] ? 0x80U : 0x00U;
byte |= bits[1U] ? 0x40U : 0x00U;
byte |= bits[2U] ? 0x20U : 0x00U;
byte |= bits[3U] ? 0x10U : 0x00U;
byte |= bits[4U] ? 0x08U : 0x00U;
byte |= bits[5U] ? 0x04U : 0x00U;
byte |= bits[6U] ? 0x02U : 0x00U;
byte |= bits[7U] ? 0x01U : 0x00U;
}
void CBPTC19696::decodeExtractBinary(const unsigned char* in)
{
// First block
byteToBitsBE(in[0U], m_rawData + 0U);
byteToBitsBE(in[1U], m_rawData + 8U);
byteToBitsBE(in[2U], m_rawData + 16U);
byteToBitsBE(in[3U], m_rawData + 24U);
byteToBitsBE(in[4U], m_rawData + 32U);
byteToBitsBE(in[5U], m_rawData + 40U);
byteToBitsBE(in[6U], m_rawData + 48U);
byteToBitsBE(in[7U], m_rawData + 56U);
byteToBitsBE(in[8U], m_rawData + 64U);
byteToBitsBE(in[9U], m_rawData + 72U);
byteToBitsBE(in[10U], m_rawData + 80U);
byteToBitsBE(in[11U], m_rawData + 88U);
byteToBitsBE(in[12U], m_rawData + 96U);
// Handle the two bits
bool bits[8U];
byteToBitsBE(in[20U], bits);
m_rawData[98U] = bits[6U];
m_rawData[99U] = bits[7U];
// Second block
byteToBitsBE(in[21U], m_rawData + 100U);
byteToBitsBE(in[22U], m_rawData + 108U);
byteToBitsBE(in[23U], m_rawData + 116U);
byteToBitsBE(in[24U], m_rawData + 124U);
byteToBitsBE(in[25U], m_rawData + 132U);
byteToBitsBE(in[26U], m_rawData + 140U);
byteToBitsBE(in[27U], m_rawData + 148U);
byteToBitsBE(in[28U], m_rawData + 156U);
byteToBitsBE(in[29U], m_rawData + 164U);
byteToBitsBE(in[30U], m_rawData + 172U);
byteToBitsBE(in[31U], m_rawData + 180U);
byteToBitsBE(in[32U], m_rawData + 188U);
}
// Deinterleave the raw data
void CBPTC19696::decodeDeInterleave()
{
for (unsigned int i = 0U; i < 196U; i++)
m_deInterData[i] = false;
// The first bit is R(3) which is not used so can be ignored
for (unsigned int a = 0U; a < 196U; a++) {
// Calculate the interleave sequence
unsigned int interleaveSequence = (a * 181U) % 196U;
// Shuffle the data
m_deInterData[a] = m_rawData[interleaveSequence];
}
}
// Check each row with a Hamming (15,11,3) code and each column with a Hamming (13,9,3) code
void CBPTC19696::decodeErrorCheck()
{
bool fixing;
unsigned int count = 0U;
do {
fixing = false;
// Run through each of the 15 columns
bool col[13U];
for (unsigned int c = 0U; c < 15U; c++) {
unsigned int pos = c + 1U;
for (unsigned int a = 0U; a < 13U; a++) {
col[a] = m_deInterData[pos];
pos = pos + 15U;
}
if (CHamming::decode1393(col)) {
unsigned int pos = c + 1U;
for (unsigned int a = 0U; a < 13U; a++) {
m_deInterData[pos] = col[a];
pos = pos + 15U;
}
fixing = true;
}
}
// Run through each of the 9 rows containing data
for (unsigned int r = 0U; r < 9U; r++) {
unsigned int pos = (r * 15U) + 1U;
if (CHamming::decode15113_2(m_deInterData + pos))
fixing = true;
}
count++;
} while (fixing && count < 5U);
}
// Extract the 96 bits of payload
void CBPTC19696::decodeExtractData(unsigned char* data)
{
bool bData[96U];
unsigned int pos = 0U;
for (unsigned int a = 4U; a <= 11U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 16U; a <= 26U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 31U; a <= 41U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 46U; a <= 56U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 61U; a <= 71U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 76U; a <= 86U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 91U; a <= 101U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 106U; a <= 116U; a++, pos++)
bData[pos] = m_deInterData[a];
for (unsigned int a = 121U; a <= 131U; a++, pos++)
bData[pos] = m_deInterData[a];
bitsToByteBE(bData + 0U, data[0U]);
bitsToByteBE(bData + 8U, data[1U]);
bitsToByteBE(bData + 16U, data[2U]);
bitsToByteBE(bData + 24U, data[3U]);
bitsToByteBE(bData + 32U, data[4U]);
bitsToByteBE(bData + 40U, data[5U]);
bitsToByteBE(bData + 48U, data[6U]);
bitsToByteBE(bData + 56U, data[7U]);
bitsToByteBE(bData + 64U, data[8U]);
bitsToByteBE(bData + 72U, data[9U]);
bitsToByteBE(bData + 80U, data[10U]);
bitsToByteBE(bData + 88U, data[11U]);
}
// Extract the 96 bits of payload
void CBPTC19696::encodeExtractData(const unsigned char* in)
{
bool bData[96U];
byteToBitsBE(in[0U], bData + 0U);
byteToBitsBE(in[1U], bData + 8U);
byteToBitsBE(in[2U], bData + 16U);
byteToBitsBE(in[3U], bData + 24U);
byteToBitsBE(in[4U], bData + 32U);
byteToBitsBE(in[5U], bData + 40U);
byteToBitsBE(in[6U], bData + 48U);
byteToBitsBE(in[7U], bData + 56U);
byteToBitsBE(in[8U], bData + 64U);
byteToBitsBE(in[9U], bData + 72U);
byteToBitsBE(in[10U], bData + 80U);
byteToBitsBE(in[11U], bData + 88U);
for (unsigned int i = 0U; i < 196U; i++)
m_deInterData[i] = false;
unsigned int pos = 0U;
for (unsigned int a = 4U; a <= 11U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 16U; a <= 26U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 31U; a <= 41U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 46U; a <= 56U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 61U; a <= 71U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 76U; a <= 86U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 91U; a <= 101U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 106U; a <= 116U; a++, pos++)
m_deInterData[a] = bData[pos];
for (unsigned int a = 121U; a <= 131U; a++, pos++)
m_deInterData[a] = bData[pos];
}
// Check each row with a Hamming (15,11,3) code and each column with a Hamming (13,9,3) code
void CBPTC19696::encodeErrorCheck()
{
// Run through each of the 9 rows containing data
for (unsigned int r = 0U; r < 9U; r++) {
unsigned int pos = (r * 15U) + 1U;
CHamming::encode15113_2(m_deInterData + pos);
}
// Run through each of the 15 columns
bool col[13U];
for (unsigned int c = 0U; c < 15U; c++) {
unsigned int pos = c + 1U;
for (unsigned int a = 0U; a < 13U; a++) {
col[a] = m_deInterData[pos];
pos = pos + 15U;
}
CHamming::encode1393(col);
pos = c + 1U;
for (unsigned int a = 0U; a < 13U; a++) {
m_deInterData[pos] = col[a];
pos = pos + 15U;
}
}
}
// Interleave the raw data
void CBPTC19696::encodeInterleave()
{
for (unsigned int i = 0U; i < 196U; i++)
m_rawData[i] = false;
// The first bit is R(3) which is not used so can be ignored
for (unsigned int a = 0U; a < 196U; a++) {
// Calculate the interleave sequence
unsigned int interleaveSequence = (a * 181U) % 196U;
// Unshuffle the data
m_rawData[interleaveSequence] = m_deInterData[a];
}
}
void CBPTC19696::encodeExtractBinary(unsigned char* data)
{
// First block
bitsToByteBE(m_rawData + 0U, data[0U]);
bitsToByteBE(m_rawData + 8U, data[1U]);
bitsToByteBE(m_rawData + 16U, data[2U]);
bitsToByteBE(m_rawData + 24U, data[3U]);
bitsToByteBE(m_rawData + 32U, data[4U]);
bitsToByteBE(m_rawData + 40U, data[5U]);
bitsToByteBE(m_rawData + 48U, data[6U]);
bitsToByteBE(m_rawData + 56U, data[7U]);
bitsToByteBE(m_rawData + 64U, data[8U]);
bitsToByteBE(m_rawData + 72U, data[9U]);
bitsToByteBE(m_rawData + 80U, data[10U]);
bitsToByteBE(m_rawData + 88U, data[11U]);
// Handle the two bits
unsigned char byte;
bitsToByteBE(m_rawData + 96U, byte);
data[12U] = (data[12U] & 0x3FU) | ((byte >> 0) & 0xC0U);
data[20U] = (data[20U] & 0xFCU) | ((byte >> 4) & 0x03U);
// Second block
bitsToByteBE(m_rawData + 100U, data[21U]);
bitsToByteBE(m_rawData + 108U, data[22U]);
bitsToByteBE(m_rawData + 116U, data[23U]);
bitsToByteBE(m_rawData + 124U, data[24U]);
bitsToByteBE(m_rawData + 132U, data[25U]);
bitsToByteBE(m_rawData + 140U, data[26U]);
bitsToByteBE(m_rawData + 148U, data[27U]);
bitsToByteBE(m_rawData + 156U, data[28U]);
bitsToByteBE(m_rawData + 164U, data[29U]);
bitsToByteBE(m_rawData + 172U, data[30U]);
bitsToByteBE(m_rawData + 180U, data[31U]);
bitsToByteBE(m_rawData + 188U, data[32U]);
}

@ -0,0 +1,49 @@
/*
* Copyright (C) 2015 by Jonathan Naylor G4KLX
*
* 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.
*/
#if !defined(BPTC19696_H)
#define BPTC19696_H
class CBPTC19696
{
public:
CBPTC19696();
~CBPTC19696();
void decode(const unsigned char* in, unsigned char* out);
void encode(const unsigned char* in, unsigned char* out);
private:
bool m_rawData[196];
bool m_deInterData[196];
void decodeExtractBinary(const unsigned char* in);
void decodeErrorCheck();
void decodeDeInterleave();
void decodeExtractData(unsigned char* data);
void encodeExtractData(const unsigned char* in);
void encodeInterleave();
void encodeErrorCheck();
void encodeExtractBinary(unsigned char* data);
void byteToBitsBE(unsigned char byte, bool* bits);
void bitsToByteBE(bool* bits, unsigned char& byte);
};
#endif

@ -0,0 +1,262 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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 "cgolay2087.h"
#include <cstdio>
#include <cassert>
const unsigned int ENCODING_TABLE_2087[] =
{0x0000U, 0xB08EU, 0xE093U, 0x501DU, 0x70A9U, 0xC027U, 0x903AU, 0x20B4U, 0x60DCU, 0xD052U, 0x804FU, 0x30C1U,
0x1075U, 0xA0FBU, 0xF0E6U, 0x4068U, 0x7036U, 0xC0B8U, 0x90A5U, 0x202BU, 0x009FU, 0xB011U, 0xE00CU, 0x5082U,
0x10EAU, 0xA064U, 0xF079U, 0x40F7U, 0x6043U, 0xD0CDU, 0x80D0U, 0x305EU, 0xD06CU, 0x60E2U, 0x30FFU, 0x8071U,
0xA0C5U, 0x104BU, 0x4056U, 0xF0D8U, 0xB0B0U, 0x003EU, 0x5023U, 0xE0ADU, 0xC019U, 0x7097U, 0x208AU, 0x9004U,
0xA05AU, 0x10D4U, 0x40C9U, 0xF047U, 0xD0F3U, 0x607DU, 0x3060U, 0x80EEU, 0xC086U, 0x7008U, 0x2015U, 0x909BU,
0xB02FU, 0x00A1U, 0x50BCU, 0xE032U, 0x90D9U, 0x2057U, 0x704AU, 0xC0C4U, 0xE070U, 0x50FEU, 0x00E3U, 0xB06DU,
0xF005U, 0x408BU, 0x1096U, 0xA018U, 0x80ACU, 0x3022U, 0x603FU, 0xD0B1U, 0xE0EFU, 0x5061U, 0x007CU, 0xB0F2U,
0x9046U, 0x20C8U, 0x70D5U, 0xC05BU, 0x8033U, 0x30BDU, 0x60A0U, 0xD02EU, 0xF09AU, 0x4014U, 0x1009U, 0xA087U,
0x40B5U, 0xF03BU, 0xA026U, 0x10A8U, 0x301CU, 0x8092U, 0xD08FU, 0x6001U, 0x2069U, 0x90E7U, 0xC0FAU, 0x7074U,
0x50C0U, 0xE04EU, 0xB053U, 0x00DDU, 0x3083U, 0x800DU, 0xD010U, 0x609EU, 0x402AU, 0xF0A4U, 0xA0B9U, 0x1037U,
0x505FU, 0xE0D1U, 0xB0CCU, 0x0042U, 0x20F6U, 0x9078U, 0xC065U, 0x70EBU, 0xA03DU, 0x10B3U, 0x40AEU, 0xF020U,
0xD094U, 0x601AU, 0x3007U, 0x8089U, 0xC0E1U, 0x706FU, 0x2072U, 0x90FCU, 0xB048U, 0x00C6U, 0x50DBU, 0xE055U,
0xD00BU, 0x6085U, 0x3098U, 0x8016U, 0xA0A2U, 0x102CU, 0x4031U, 0xF0BFU, 0xB0D7U, 0x0059U, 0x5044U, 0xE0CAU,
0xC07EU, 0x70F0U, 0x20EDU, 0x9063U, 0x7051U, 0xC0DFU, 0x90C2U, 0x204CU, 0x00F8U, 0xB076U, 0xE06BU, 0x50E5U,
0x108DU, 0xA003U, 0xF01EU, 0x4090U, 0x6024U, 0xD0AAU, 0x80B7U, 0x3039U, 0x0067U, 0xB0E9U, 0xE0F4U, 0x507AU,
0x70CEU, 0xC040U, 0x905DU, 0x20D3U, 0x60BBU, 0xD035U, 0x8028U, 0x30A6U, 0x1012U, 0xA09CU, 0xF081U, 0x400FU,
0x30E4U, 0x806AU, 0xD077U, 0x60F9U, 0x404DU, 0xF0C3U, 0xA0DEU, 0x1050U, 0x5038U, 0xE0B6U, 0xB0ABU, 0x0025U,
0x2091U, 0x901FU, 0xC002U, 0x708CU, 0x40D2U, 0xF05CU, 0xA041U, 0x10CFU, 0x307BU, 0x80F5U, 0xD0E8U, 0x6066U,
0x200EU, 0x9080U, 0xC09DU, 0x7013U, 0x50A7U, 0xE029U, 0xB034U, 0x00BAU, 0xE088U, 0x5006U, 0x001BU, 0xB095U,
0x9021U, 0x20AFU, 0x70B2U, 0xC03CU, 0x8054U, 0x30DAU, 0x60C7U, 0xD049U, 0xF0FDU, 0x4073U, 0x106EU, 0xA0E0U,
0x90BEU, 0x2030U, 0x702DU, 0xC0A3U, 0xE017U, 0x5099U, 0x0084U, 0xB00AU, 0xF062U, 0x40ECU, 0x10F1U, 0xA07FU,
0x80CBU, 0x3045U, 0x6058U, 0xD0D6U};
const unsigned int DECODING_TABLE_1987[] =
{0x00000U, 0x00001U, 0x00002U, 0x00003U, 0x00004U, 0x00005U, 0x00006U, 0x00007U, 0x00008U, 0x00009U, 0x0000AU, 0x0000BU, 0x0000CU,
0x0000DU, 0x0000EU, 0x24020U, 0x00010U, 0x00011U, 0x00012U, 0x00013U, 0x00014U, 0x00015U, 0x00016U, 0x00017U, 0x00018U, 0x00019U,
0x0001AU, 0x0001BU, 0x0001CU, 0x0001DU, 0x48040U, 0x01480U, 0x00020U, 0x00021U, 0x00022U, 0x00023U, 0x00024U, 0x00025U, 0x00026U,
0x24008U, 0x00028U, 0x00029U, 0x0002AU, 0x24004U, 0x0002CU, 0x24002U, 0x24001U, 0x24000U, 0x00030U, 0x00031U, 0x00032U, 0x08180U,
0x00034U, 0x00C40U, 0x00036U, 0x00C42U, 0x00038U, 0x43000U, 0x0003AU, 0x43002U, 0x02902U, 0x24012U, 0x02900U, 0x24010U, 0x00040U,
0x00041U, 0x00042U, 0x00043U, 0x00044U, 0x00045U, 0x00046U, 0x00047U, 0x00048U, 0x00049U, 0x0004AU, 0x02500U, 0x0004CU, 0x0004DU,
0x48010U, 0x48011U, 0x00050U, 0x00051U, 0x00052U, 0x21200U, 0x00054U, 0x00C20U, 0x48008U, 0x48009U, 0x00058U, 0x00059U, 0x48004U,
0x48005U, 0x48002U, 0x48003U, 0x48000U, 0x48001U, 0x00060U, 0x00061U, 0x00062U, 0x00063U, 0x00064U, 0x00C10U, 0x10300U, 0x0B000U,
0x00068U, 0x00069U, 0x01880U, 0x01881U, 0x40181U, 0x40180U, 0x24041U, 0x24040U, 0x00070U, 0x00C04U, 0x00072U, 0x00C06U, 0x00C01U,
0x00C00U, 0x00C03U, 0x00C02U, 0x05204U, 0x00C0CU, 0x48024U, 0x48025U, 0x05200U, 0x00C08U, 0x48020U, 0x48021U, 0x00080U, 0x00081U,
0x00082U, 0x00083U, 0x00084U, 0x00085U, 0x00086U, 0x00087U, 0x00088U, 0x00089U, 0x0008AU, 0x50200U, 0x0008CU, 0x0A800U, 0x01411U,
0x01410U, 0x00090U, 0x00091U, 0x00092U, 0x08120U, 0x00094U, 0x00095U, 0x04A00U, 0x01408U, 0x00098U, 0x00099U, 0x01405U, 0x01404U,
0x01403U, 0x01402U, 0x01401U, 0x01400U, 0x000A0U, 0x000A1U, 0x000A2U, 0x08110U, 0x000A4U, 0x000A5U, 0x42400U, 0x42401U, 0x000A8U,
0x000A9U, 0x01840U, 0x01841U, 0x40141U, 0x40140U, 0x24081U, 0x24080U, 0x000B0U, 0x08102U, 0x08101U, 0x08100U, 0x000B4U, 0x08106U,
0x08105U, 0x08104U, 0x20A01U, 0x20A00U, 0x08109U, 0x08108U, 0x01423U, 0x01422U, 0x01421U, 0x01420U, 0x000C0U, 0x000C1U, 0x000C2U,
0x000C3U, 0x000C4U, 0x000C5U, 0x000C6U, 0x000C7U, 0x000C8U, 0x000C9U, 0x01820U, 0x01821U, 0x20600U, 0x40120U, 0x16000U, 0x16001U,
0x000D0U, 0x000D1U, 0x42801U, 0x42800U, 0x03100U, 0x18200U, 0x03102U, 0x18202U, 0x000D8U, 0x000D9U, 0x48084U, 0x01444U, 0x48082U,
0x01442U, 0x48080U, 0x01440U, 0x000E0U, 0x32000U, 0x01808U, 0x04600U, 0x40109U, 0x40108U, 0x0180CU, 0x4010AU, 0x01802U, 0x40104U,
0x01800U, 0x01801U, 0x40101U, 0x40100U, 0x01804U, 0x40102U, 0x0A408U, 0x08142U, 0x08141U, 0x08140U, 0x00C81U, 0x00C80U, 0x00C83U,
0x00C82U, 0x0A400U, 0x0A401U, 0x01810U, 0x01811U, 0x40111U, 0x40110U, 0x01814U, 0x40112U, 0x00100U, 0x00101U, 0x00102U, 0x00103U,
0x00104U, 0x00105U, 0x00106U, 0x41800U, 0x00108U, 0x00109U, 0x0010AU, 0x02440U, 0x0010CU, 0x0010DU, 0x0010EU, 0x02444U, 0x00110U,
0x00111U, 0x00112U, 0x080A0U, 0x00114U, 0x00115U, 0x00116U, 0x080A4U, 0x00118U, 0x00119U, 0x15000U, 0x15001U, 0x02822U, 0x02823U,
0x02820U, 0x02821U, 0x00120U, 0x00121U, 0x00122U, 0x08090U, 0x00124U, 0x00125U, 0x10240U, 0x10241U, 0x00128U, 0x00129U, 0x0012AU,
0x24104U, 0x09400U, 0x400C0U, 0x02810U, 0x24100U, 0x00130U, 0x08082U, 0x08081U, 0x08080U, 0x31001U, 0x31000U, 0x02808U, 0x08084U,
0x02806U, 0x0808AU, 0x02804U, 0x08088U, 0x02802U, 0x02803U, 0x02800U, 0x02801U, 0x00140U, 0x00141U, 0x00142U, 0x02408U, 0x00144U,
0x00145U, 0x10220U, 0x10221U, 0x00148U, 0x02402U, 0x02401U, 0x02400U, 0x400A1U, 0x400A0U, 0x02405U, 0x02404U, 0x00150U, 0x00151U,
0x00152U, 0x02418U, 0x03080U, 0x03081U, 0x03082U, 0x03083U, 0x09801U, 0x09800U, 0x02411U, 0x02410U, 0x48102U, 0x09804U, 0x48100U,
0x48101U, 0x00160U, 0x00161U, 0x10204U, 0x10205U, 0x10202U, 0x40088U, 0x10200U, 0x10201U, 0x40085U, 0x40084U, 0x02421U, 0x02420U,
0x40081U, 0x40080U, 0x10208U, 0x40082U, 0x41402U, 0x080C2U, 0x41400U, 0x080C0U, 0x00D01U, 0x00D00U, 0x10210U, 0x10211U, 0x40095U,
0x40094U, 0x02844U, 0x080C8U, 0x40091U, 0x40090U, 0x02840U, 0x02841U, 0x00180U, 0x00181U, 0x00182U, 0x08030U, 0x00184U, 0x14400U,
0x22201U, 0x22200U, 0x00188U, 0x00189U, 0x0018AU, 0x08038U, 0x40061U, 0x40060U, 0x40063U, 0x40062U, 0x00190U, 0x08022U, 0x08021U,
0x08020U, 0x03040U, 0x03041U, 0x08025U, 0x08024U, 0x40C00U, 0x40C01U, 0x08029U, 0x08028U, 0x2C000U, 0x2C001U, 0x01501U, 0x01500U,
0x001A0U, 0x08012U, 0x08011U, 0x08010U, 0x40049U, 0x40048U, 0x08015U, 0x08014U, 0x06200U, 0x40044U, 0x30400U, 0x08018U, 0x40041U,
0x40040U, 0x40043U, 0x40042U, 0x08003U, 0x08002U, 0x08001U, 0x08000U, 0x08007U, 0x08006U, 0x08005U, 0x08004U, 0x0800BU, 0x0800AU,
0x08009U, 0x08008U, 0x40051U, 0x40050U, 0x02880U, 0x0800CU, 0x001C0U, 0x001C1U, 0x64000U, 0x64001U, 0x03010U, 0x40028U, 0x08C00U,
0x08C01U, 0x40025U, 0x40024U, 0x02481U, 0x02480U, 0x40021U, 0x40020U, 0x40023U, 0x40022U, 0x03004U, 0x03005U, 0x08061U, 0x08060U,
0x03000U, 0x03001U, 0x03002U, 0x03003U, 0x0300CU, 0x40034U, 0x30805U, 0x30804U, 0x03008U, 0x40030U, 0x30801U, 0x30800U, 0x4000DU,
0x4000CU, 0x08051U, 0x08050U, 0x40009U, 0x40008U, 0x10280U, 0x4000AU, 0x40005U, 0x40004U, 0x01900U, 0x40006U, 0x40001U, 0x40000U,
0x40003U, 0x40002U, 0x14800U, 0x08042U, 0x08041U, 0x08040U, 0x03020U, 0x40018U, 0x08045U, 0x08044U, 0x40015U, 0x40014U, 0x08049U,
0x08048U, 0x40011U, 0x40010U, 0x40013U, 0x40012U, 0x00200U, 0x00201U, 0x00202U, 0x00203U, 0x00204U, 0x00205U, 0x00206U, 0x00207U,
0x00208U, 0x00209U, 0x0020AU, 0x50080U, 0x0020CU, 0x0020DU, 0x0020EU, 0x50084U, 0x00210U, 0x00211U, 0x00212U, 0x21040U, 0x00214U,
0x00215U, 0x04880U, 0x04881U, 0x00218U, 0x00219U, 0x0E001U, 0x0E000U, 0x0021CU, 0x0021DU, 0x04888U, 0x0E004U, 0x00220U, 0x00221U,
0x00222U, 0x00223U, 0x00224U, 0x00225U, 0x10140U, 0x10141U, 0x00228U, 0x00229U, 0x0022AU, 0x24204U, 0x12401U, 0x12400U, 0x24201U,
0x24200U, 0x00230U, 0x00231U, 0x00232U, 0x21060U, 0x2A000U, 0x2A001U, 0x2A002U, 0x2A003U, 0x20881U, 0x20880U, 0x20883U, 0x20882U,
0x05040U, 0x05041U, 0x05042U, 0x24210U, 0x00240U, 0x00241U, 0x00242U, 0x21010U, 0x00244U, 0x46000U, 0x10120U, 0x10121U, 0x00248U,
0x00249U, 0x0024AU, 0x21018U, 0x20480U, 0x20481U, 0x20482U, 0x20483U, 0x00250U, 0x21002U, 0x21001U, 0x21000U, 0x18081U, 0x18080U,
0x21005U, 0x21004U, 0x12800U, 0x12801U, 0x21009U, 0x21008U, 0x05020U, 0x05021U, 0x48200U, 0x48201U, 0x00260U, 0x00261U, 0x10104U,
0x04480U, 0x10102U, 0x10103U, 0x10100U, 0x10101U, 0x62002U, 0x62003U, 0x62000U, 0x62001U, 0x05010U, 0x05011U, 0x10108U, 0x10109U,
0x0500CU, 0x21022U, 0x21021U, 0x21020U, 0x05008U, 0x00E00U, 0x10110U, 0x10111U, 0x05004U, 0x05005U, 0x05006U, 0x21028U, 0x05000U,
0x05001U, 0x05002U, 0x05003U, 0x00280U, 0x00281U, 0x00282U, 0x50008U, 0x00284U, 0x00285U, 0x04810U, 0x22100U, 0x00288U, 0x50002U,
0x50001U, 0x50000U, 0x20440U, 0x20441U, 0x50005U, 0x50004U, 0x00290U, 0x00291U, 0x04804U, 0x04805U, 0x04802U, 0x18040U, 0x04800U,
0x04801U, 0x20821U, 0x20820U, 0x50011U, 0x50010U, 0x0480AU, 0x01602U, 0x04808U, 0x01600U, 0x002A0U, 0x002A1U, 0x04441U, 0x04440U,
0x002A4U, 0x002A5U, 0x04830U, 0x04444U, 0x06100U, 0x20810U, 0x50021U, 0x50020U, 0x06104U, 0x20814U, 0x50025U, 0x50024U, 0x20809U,
0x20808U, 0x13000U, 0x08300U, 0x04822U, 0x2080CU, 0x04820U, 0x04821U, 0x20801U, 0x20800U, 0x20803U, 0x20802U, 0x20805U, 0x20804U,
0x04828U, 0x20806U, 0x002C0U, 0x002C1U, 0x04421U, 0x04420U, 0x20408U, 0x18010U, 0x2040AU, 0x18012U, 0x20404U, 0x20405U, 0x50041U,
0x50040U, 0x20400U, 0x20401U, 0x20402U, 0x20403U, 0x18005U, 0x18004U, 0x21081U, 0x21080U, 0x18001U, 0x18000U, 0x04840U, 0x18002U,
0x20414U, 0x1800CU, 0x21089U, 0x21088U, 0x20410U, 0x18008U, 0x20412U, 0x1800AU, 0x04403U, 0x04402U, 0x04401U, 0x04400U, 0x10182U,
0x04406U, 0x10180U, 0x04404U, 0x01A02U, 0x0440AU, 0x01A00U, 0x04408U, 0x20420U, 0x40300U, 0x20422U, 0x40302U, 0x04413U, 0x04412U,
0x04411U, 0x04410U, 0x18021U, 0x18020U, 0x10190U, 0x18022U, 0x20841U, 0x20840U, 0x01A10U, 0x20842U, 0x05080U, 0x05081U, 0x05082U,
0x05083U, 0x00300U, 0x00301U, 0x00302U, 0x00303U, 0x00304U, 0x00305U, 0x10060U, 0x22080U, 0x00308U, 0x00309U, 0x28800U, 0x28801U,
0x44402U, 0x44403U, 0x44400U, 0x44401U, 0x00310U, 0x00311U, 0x10C01U, 0x10C00U, 0x00314U, 0x00315U, 0x10070U, 0x10C04U, 0x00318U,
0x00319U, 0x28810U, 0x10C08U, 0x44412U, 0x00000U, 0x44410U, 0x44411U, 0x00320U, 0x60400U, 0x10044U, 0x10045U, 0x10042U, 0x0C800U,
0x10040U, 0x10041U, 0x06080U, 0x06081U, 0x06082U, 0x06083U, 0x1004AU, 0x0C808U, 0x10048U, 0x10049U, 0x58008U, 0x08282U, 0x08281U,
0x08280U, 0x10052U, 0x0C810U, 0x10050U, 0x10051U, 0x58000U, 0x58001U, 0x58002U, 0x08288U, 0x02A02U, 0x02A03U, 0x02A00U, 0x02A01U,
0x00340U, 0x00341U, 0x10024U, 0x10025U, 0x10022U, 0x10023U, 0x10020U, 0x10021U, 0x34001U, 0x34000U, 0x02601U, 0x02600U, 0x1002AU,
0x34004U, 0x10028U, 0x10029U, 0x0C400U, 0x0C401U, 0x21101U, 0x21100U, 0x60800U, 0x60801U, 0x10030U, 0x10031U, 0x0C408U, 0x34010U,
0x21109U, 0x21108U, 0x60808U, 0x60809U, 0x10038U, 0x28420U, 0x10006U, 0x10007U, 0x10004U, 0x10005U, 0x10002U, 0x10003U, 0x10000U,
0x10001U, 0x1000EU, 0x40284U, 0x1000CU, 0x1000DU, 0x1000AU, 0x40280U, 0x10008U, 0x10009U, 0x10016U, 0x10017U, 0x10014U, 0x10015U,
0x10012U, 0x10013U, 0x10010U, 0x10011U, 0x05104U, 0x44802U, 0x44801U, 0x44800U, 0x05100U, 0x05101U, 0x10018U, 0x28400U, 0x00380U,
0x00381U, 0x22005U, 0x22004U, 0x22003U, 0x22002U, 0x22001U, 0x22000U, 0x06020U, 0x06021U, 0x50101U, 0x50100U, 0x11800U, 0x11801U,
0x22009U, 0x22008U, 0x45001U, 0x45000U, 0x08221U, 0x08220U, 0x04902U, 0x22012U, 0x04900U, 0x22010U, 0x06030U, 0x45008U, 0x08229U,
0x08228U, 0x11810U, 0x11811U, 0x04908U, 0x22018U, 0x06008U, 0x06009U, 0x08211U, 0x08210U, 0x100C2U, 0x22022U, 0x100C0U, 0x22020U,
0x06000U, 0x06001U, 0x06002U, 0x06003U, 0x06004U, 0x40240U, 0x06006U, 0x40242U, 0x08203U, 0x08202U, 0x08201U, 0x08200U, 0x08207U,
0x08206U, 0x08205U, 0x08204U, 0x06010U, 0x20900U, 0x08209U, 0x08208U, 0x61002U, 0x20904U, 0x61000U, 0x61001U, 0x29020U, 0x29021U,
0x100A4U, 0x22044U, 0x100A2U, 0x22042U, 0x100A0U, 0x22040U, 0x20504U, 0x40224U, 0x0D005U, 0x0D004U, 0x20500U, 0x40220U, 0x0D001U,
0x0D000U, 0x03204U, 0x18104U, 0x08261U, 0x08260U, 0x03200U, 0x18100U, 0x03202U, 0x18102U, 0x11421U, 0x11420U, 0x00000U, 0x11422U,
0x03208U, 0x18108U, 0x0D011U, 0x0D010U, 0x29000U, 0x29001U, 0x10084U, 0x04500U, 0x10082U, 0x40208U, 0x10080U, 0x10081U, 0x06040U,
0x40204U, 0x06042U, 0x40206U, 0x40201U, 0x40200U, 0x10088U, 0x40202U, 0x29010U, 0x08242U, 0x08241U, 0x08240U, 0x10092U, 0x40218U,
0x10090U, 0x10091U, 0x11401U, 0x11400U, 0x11403U, 0x11402U, 0x40211U, 0x40210U, 0x10098U, 0x40212U, 0x00400U, 0x00401U, 0x00402U,
0x00403U, 0x00404U, 0x00405U, 0x00406U, 0x00407U, 0x00408U, 0x00409U, 0x0040AU, 0x02140U, 0x0040CU, 0x0040DU, 0x01091U, 0x01090U,
0x00410U, 0x00411U, 0x00412U, 0x00413U, 0x00414U, 0x00860U, 0x01089U, 0x01088U, 0x00418U, 0x38000U, 0x01085U, 0x01084U, 0x01083U,
0x01082U, 0x01081U, 0x01080U, 0x00420U, 0x00421U, 0x00422U, 0x00423U, 0x00424U, 0x00850U, 0x42080U, 0x42081U, 0x00428U, 0x00429U,
0x48801U, 0x48800U, 0x09100U, 0x12200U, 0x24401U, 0x24400U, 0x00430U, 0x00844U, 0x00432U, 0x00846U, 0x00841U, 0x00840U, 0x1C000U,
0x00842U, 0x00438U, 0x0084CU, 0x010A5U, 0x010A4U, 0x00849U, 0x00848U, 0x010A1U, 0x010A0U, 0x00440U, 0x00441U, 0x00442U, 0x02108U,
0x00444U, 0x00830U, 0x70001U, 0x70000U, 0x00448U, 0x02102U, 0x02101U, 0x02100U, 0x20280U, 0x20281U, 0x02105U, 0x02104U, 0x00450U,
0x00824U, 0x00452U, 0x00826U, 0x00821U, 0x00820U, 0x00823U, 0x00822U, 0x24802U, 0x02112U, 0x24800U, 0x02110U, 0x00829U, 0x00828U,
0x48400U, 0x010C0U, 0x00460U, 0x00814U, 0x04281U, 0x04280U, 0x00811U, 0x00810U, 0x00813U, 0x00812U, 0x54000U, 0x54001U, 0x02121U,
0x02120U, 0x00819U, 0x00818U, 0x0081BU, 0x0081AU, 0x00805U, 0x00804U, 0x41100U, 0x00806U, 0x00801U, 0x00800U, 0x00803U, 0x00802U,
0x0A080U, 0x0080CU, 0x0A082U, 0x0080EU, 0x00809U, 0x00808U, 0x0080BU, 0x0080AU, 0x00480U, 0x00481U, 0x00482U, 0x00483U, 0x00484U,
0x14100U, 0x42020U, 0x01018U, 0x00488U, 0x00489U, 0x01015U, 0x01014U, 0x20240U, 0x01012U, 0x01011U, 0x01010U, 0x00490U, 0x00491U,
0x0100DU, 0x0100CU, 0x0100BU, 0x0100AU, 0x01009U, 0x01008U, 0x40900U, 0x01006U, 0x01005U, 0x01004U, 0x01003U, 0x01002U, 0x01001U,
0x01000U, 0x004A0U, 0x004A1U, 0x42004U, 0x04240U, 0x42002U, 0x42003U, 0x42000U, 0x42001U, 0x30102U, 0x30103U, 0x30100U, 0x30101U,
0x4200AU, 0x01032U, 0x42008U, 0x01030U, 0x25000U, 0x25001U, 0x08501U, 0x08500U, 0x008C1U, 0x008C0U, 0x42010U, 0x01028U, 0x0A040U,
0x0A041U, 0x01025U, 0x01024U, 0x01023U, 0x01022U, 0x01021U, 0x01020U, 0x004C0U, 0x49000U, 0x04221U, 0x04220U, 0x20208U, 0x20209U,
0x08900U, 0x08901U, 0x20204U, 0x20205U, 0x02181U, 0x02180U, 0x20200U, 0x20201U, 0x20202U, 0x01050U, 0x0A028U, 0x008A4U, 0x0104DU,
0x0104CU, 0x008A1U, 0x008A0U, 0x01049U, 0x01048U, 0x0A020U, 0x0A021U, 0x01045U, 0x01044U, 0x20210U, 0x01042U, 0x01041U, 0x01040U,
0x04203U, 0x04202U, 0x04201U, 0x04200U, 0x00891U, 0x00890U, 0x42040U, 0x04204U, 0x0A010U, 0x0A011U, 0x01C00U, 0x04208U, 0x20220U,
0x40500U, 0x20222U, 0x40502U, 0x0A008U, 0x00884U, 0x04211U, 0x04210U, 0x00881U, 0x00880U, 0x00883U, 0x00882U, 0x0A000U, 0x0A001U,
0x0A002U, 0x0A003U, 0x0A004U, 0x00888U, 0x01061U, 0x01060U, 0x00500U, 0x00501U, 0x00502U, 0x02048U, 0x00504U, 0x14080U, 0x00506U,
0x14082U, 0x00508U, 0x02042U, 0x02041U, 0x02040U, 0x09020U, 0x09021U, 0x44200U, 0x02044U, 0x00510U, 0x00511U, 0x10A01U, 0x10A00U,
0x4A001U, 0x4A000U, 0x4A003U, 0x4A002U, 0x40880U, 0x40881U, 0x02051U, 0x02050U, 0x40884U, 0x01182U, 0x01181U, 0x01180U, 0x00520U,
0x60200U, 0x00522U, 0x60202U, 0x09008U, 0x09009U, 0x0900AU, 0x0900BU, 0x09004U, 0x09005U, 0x30080U, 0x02060U, 0x09000U, 0x09001U,
0x09002U, 0x09003U, 0x41042U, 0x08482U, 0x41040U, 0x08480U, 0x00941U, 0x00940U, 0x41044U, 0x00942U, 0x09014U, 0x09015U, 0x02C04U,
0x08488U, 0x09010U, 0x09011U, 0x02C00U, 0x02C01U, 0x00540U, 0x0200AU, 0x02009U, 0x02008U, 0x08882U, 0x0200EU, 0x08880U, 0x0200CU,
0x02003U, 0x02002U, 0x02001U, 0x02000U, 0x02007U, 0x02006U, 0x02005U, 0x02004U, 0x0C200U, 0x0C201U, 0x41020U, 0x02018U, 0x00921U,
0x00920U, 0x41024U, 0x00922U, 0x02013U, 0x02012U, 0x02011U, 0x02010U, 0x02017U, 0x02016U, 0x02015U, 0x02014U, 0x41012U, 0x0202AU,
0x41010U, 0x02028U, 0x26000U, 0x00910U, 0x10600U, 0x10601U, 0x02023U, 0x02022U, 0x02021U, 0x02020U, 0x09040U, 0x40480U, 0x02025U,
0x02024U, 0x41002U, 0x00904U, 0x41000U, 0x41001U, 0x00901U, 0x00900U, 0x41004U, 0x00902U, 0x4100AU, 0x02032U, 0x41008U, 0x02030U,
0x00909U, 0x00908U, 0x28201U, 0x28200U, 0x00580U, 0x14004U, 0x00582U, 0x14006U, 0x14001U, 0x14000U, 0x08840U, 0x14002U, 0x40810U,
0x40811U, 0x30020U, 0x020C0U, 0x14009U, 0x14008U, 0x01111U, 0x01110U, 0x40808U, 0x40809U, 0x08421U, 0x08420U, 0x14011U, 0x14010U,
0x01109U, 0x01108U, 0x40800U, 0x40801U, 0x40802U, 0x01104U, 0x40804U, 0x01102U, 0x01101U, 0x01100U, 0x03801U, 0x03800U, 0x30008U,
0x08410U, 0x14021U, 0x14020U, 0x42100U, 0x42101U, 0x30002U, 0x30003U, 0x30000U, 0x30001U, 0x09080U, 0x40440U, 0x30004U, 0x30005U,
0x08403U, 0x08402U, 0x08401U, 0x08400U, 0x08407U, 0x08406U, 0x08405U, 0x08404U, 0x40820U, 0x40821U, 0x30010U, 0x08408U, 0x40824U,
0x01122U, 0x01121U, 0x01120U, 0x08806U, 0x0208AU, 0x08804U, 0x02088U, 0x08802U, 0x14040U, 0x08800U, 0x08801U, 0x02083U, 0x02082U,
0x02081U, 0x02080U, 0x20300U, 0x40420U, 0x08808U, 0x02084U, 0x03404U, 0x03405U, 0x08814U, 0x02098U, 0x03400U, 0x03401U, 0x08810U,
0x08811U, 0x40840U, 0x40841U, 0x02091U, 0x02090U, 0x40844U, 0x01142U, 0x01141U, 0x01140U, 0x04303U, 0x04302U, 0x04301U, 0x04300U,
0x40409U, 0x40408U, 0x08820U, 0x08821U, 0x40405U, 0x40404U, 0x30040U, 0x020A0U, 0x40401U, 0x40400U, 0x40403U, 0x40402U, 0x41082U,
0x08442U, 0x41080U, 0x08440U, 0x00981U, 0x00980U, 0x41084U, 0x00982U, 0x0A100U, 0x11200U, 0x0A102U, 0x11202U, 0x40411U, 0x40410U,
0x40413U, 0x40412U, 0x00600U, 0x00601U, 0x00602U, 0x00603U, 0x00604U, 0x00605U, 0x00606U, 0x00607U, 0x00608U, 0x05800U, 0x0060AU,
0x05802U, 0x200C0U, 0x12020U, 0x44100U, 0x44101U, 0x00610U, 0x00611U, 0x10901U, 0x10900U, 0x51000U, 0x51001U, 0x51002U, 0x10904U,
0x00618U, 0x05810U, 0x01285U, 0x01284U, 0x51008U, 0x01282U, 0x01281U, 0x01280U, 0x00620U, 0x60100U, 0x040C1U, 0x040C0U, 0x12009U,
0x12008U, 0x21800U, 0x21801U, 0x12005U, 0x12004U, 0x12007U, 0x12006U, 0x12001U, 0x12000U, 0x12003U, 0x12002U, 0x00630U, 0x00A44U,
0x040D1U, 0x040D0U, 0x00A41U, 0x00A40U, 0x21810U, 0x00A42U, 0x12015U, 0x12014U, 0x00000U, 0x12016U, 0x12011U, 0x12010U, 0x12013U,
0x12012U, 0x00640U, 0x00641U, 0x040A1U, 0x040A0U, 0x20088U, 0x20089U, 0x2008AU, 0x040A4U, 0x20084U, 0x20085U, 0x19000U, 0x02300U,
0x20080U, 0x20081U, 0x20082U, 0x20083U, 0x0C100U, 0x0C101U, 0x21401U, 0x21400U, 0x00A21U, 0x00A20U, 0x00A23U, 0x00A22U, 0x20094U,
0x20095U, 0x19010U, 0x21408U, 0x20090U, 0x20091U, 0x20092U, 0x28120U, 0x04083U, 0x04082U, 0x04081U, 0x04080U, 0x00A11U, 0x00A10U,
0x10500U, 0x04084U, 0x200A4U, 0x0408AU, 0x04089U, 0x04088U, 0x200A0U, 0x12040U, 0x200A2U, 0x12042U, 0x00A05U, 0x00A04U, 0x04091U,
0x04090U, 0x00A01U, 0x00A00U, 0x00A03U, 0x00A02U, 0x05404U, 0x00A0CU, 0x28105U, 0x28104U, 0x05400U, 0x00A08U, 0x28101U, 0x28100U,
0x00680U, 0x00681U, 0x04061U, 0x04060U, 0x20048U, 0x20049U, 0x2004AU, 0x04064U, 0x20044U, 0x20045U, 0x50401U, 0x50400U, 0x20040U,
0x20041U, 0x20042U, 0x01210U, 0x68002U, 0x68003U, 0x68000U, 0x68001U, 0x04C02U, 0x0120AU, 0x04C00U, 0x01208U, 0x20054U, 0x01206U,
0x01205U, 0x01204U, 0x20050U, 0x01202U, 0x01201U, 0x01200U, 0x18800U, 0x04042U, 0x04041U, 0x04040U, 0x42202U, 0x04046U, 0x42200U,
0x04044U, 0x20064U, 0x0404AU, 0x04049U, 0x04048U, 0x20060U, 0x12080U, 0x20062U, 0x12082U, 0x18810U, 0x04052U, 0x04051U, 0x04050U,
0x4C009U, 0x4C008U, 0x42210U, 0x04054U, 0x20C01U, 0x20C00U, 0x20C03U, 0x20C02U, 0x4C001U, 0x4C000U, 0x01221U, 0x01220U, 0x2000CU,
0x04022U, 0x04021U, 0x04020U, 0x20008U, 0x20009U, 0x2000AU, 0x04024U, 0x20004U, 0x20005U, 0x20006U, 0x04028U, 0x20000U, 0x20001U,
0x20002U, 0x20003U, 0x2001CU, 0x04032U, 0x04031U, 0x04030U, 0x20018U, 0x18400U, 0x2001AU, 0x18402U, 0x20014U, 0x20015U, 0x20016U,
0x01244U, 0x20010U, 0x20011U, 0x20012U, 0x01240U, 0x04003U, 0x04002U, 0x04001U, 0x04000U, 0x20028U, 0x04006U, 0x04005U, 0x04004U,
0x20024U, 0x0400AU, 0x04009U, 0x04008U, 0x20020U, 0x20021U, 0x20022U, 0x0400CU, 0x04013U, 0x04012U, 0x04011U, 0x04010U, 0x00A81U,
0x00A80U, 0x04015U, 0x04014U, 0x0A200U, 0x11100U, 0x04019U, 0x04018U, 0x20030U, 0x20031U, 0x50800U, 0x50801U, 0x00700U, 0x60020U,
0x10811U, 0x10810U, 0x4400AU, 0x60024U, 0x44008U, 0x44009U, 0x44006U, 0x02242U, 0x44004U, 0x02240U, 0x44002U, 0x44003U, 0x44000U,
0x44001U, 0x0C040U, 0x10802U, 0x10801U, 0x10800U, 0x0C044U, 0x10806U, 0x10805U, 0x10804U, 0x23000U, 0x23001U, 0x10809U, 0x10808U,
0x44012U, 0x44013U, 0x44010U, 0x44011U, 0x60001U, 0x60000U, 0x60003U, 0x60002U, 0x60005U, 0x60004U, 0x10440U, 0x10441U, 0x60009U,
0x60008U, 0x44024U, 0x6000AU, 0x09200U, 0x12100U, 0x44020U, 0x44021U, 0x60011U, 0x60010U, 0x10821U, 0x10820U, 0x07003U, 0x07002U,
0x07001U, 0x07000U, 0x23020U, 0x60018U, 0x28045U, 0x28044U, 0x09210U, 0x28042U, 0x28041U, 0x28040U, 0x0C010U, 0x0C011U, 0x02209U,
0x02208U, 0x10422U, 0x10423U, 0x10420U, 0x10421U, 0x02203U, 0x02202U, 0x02201U, 0x02200U, 0x20180U, 0x20181U, 0x44040U, 0x02204U,
0x0C000U, 0x0C001U, 0x0C002U, 0x10840U, 0x0C004U, 0x0C005U, 0x0C006U, 0x10844U, 0x0C008U, 0x0C009U, 0x02211U, 0x02210U, 0x0C00CU,
0x28022U, 0x28021U, 0x28020U, 0x60041U, 0x60040U, 0x10404U, 0x04180U, 0x10402U, 0x10403U, 0x10400U, 0x10401U, 0x02223U, 0x02222U,
0x02221U, 0x02220U, 0x1040AU, 0x28012U, 0x10408U, 0x28010U, 0x0C020U, 0x0C021U, 0x41200U, 0x41201U, 0x00B01U, 0x00B00U, 0x10410U,
0x28008U, 0x11081U, 0x11080U, 0x28005U, 0x28004U, 0x28003U, 0x28002U, 0x28001U, 0x28000U, 0x52040U, 0x14204U, 0x22405U, 0x22404U,
0x14201U, 0x14200U, 0x22401U, 0x22400U, 0x20144U, 0x20145U, 0x44084U, 0x022C0U, 0x20140U, 0x20141U, 0x44080U, 0x44081U, 0x40A08U,
0x10882U, 0x10881U, 0x10880U, 0x14211U, 0x14210U, 0x1A008U, 0x10884U, 0x40A00U, 0x40A01U, 0x40A02U, 0x01304U, 0x1A002U, 0x01302U,
0x1A000U, 0x01300U, 0x60081U, 0x60080U, 0x04141U, 0x04140U, 0x60085U, 0x60084U, 0x104C0U, 0x04144U, 0x06400U, 0x06401U, 0x30200U,
0x30201U, 0x06404U, 0x40640U, 0x30204U, 0x30205U, 0x08603U, 0x08602U, 0x08601U, 0x08600U, 0x00000U, 0x08606U, 0x08605U, 0x08604U,
0x11041U, 0x11040U, 0x30210U, 0x11042U, 0x11045U, 0x11044U, 0x1A020U, 0x01320U, 0x52000U, 0x52001U, 0x04121U, 0x04120U, 0x20108U,
0x20109U, 0x08A00U, 0x08A01U, 0x20104U, 0x20105U, 0x02281U, 0x02280U, 0x20100U, 0x20101U, 0x20102U, 0x20103U, 0x0C080U, 0x0C081U,
0x0C082U, 0x04130U, 0x0C084U, 0x06808U, 0x08A10U, 0x08A11U, 0x11021U, 0x11020U, 0x11023U, 0x11022U, 0x20110U, 0x06800U, 0x20112U,
0x06802U, 0x04103U, 0x04102U, 0x04101U, 0x04100U, 0x10482U, 0x04106U, 0x10480U, 0x04104U, 0x11011U, 0x11010U, 0x04109U, 0x04108U,
0x20120U, 0x40600U, 0x20122U, 0x40602U, 0x11009U, 0x11008U, 0x22800U, 0x04110U, 0x1100DU, 0x1100CU, 0x22804U, 0x04114U, 0x11001U,
0x11000U, 0x11003U, 0x11002U, 0x11005U, 0x11004U, 0x28081U, 0x28080U};
#define X18 0x00040000 /* vector representation of X^{18} */
#define X11 0x00000800 /* vector representation of X^{11} */
#define MASK8 0xfffff800 /* auxiliary vector for testing */
#define GENPOL 0x00000c75 /* generator polinomial, g(x) */
unsigned int CGolay2087::getSyndrome1987(unsigned int pattern)
/*
* Compute the syndrome corresponding to the given pattern, i.e., the
* remainder after dividing the pattern (when considering it as the vector
* representation of a polynomial) by the generator polynomial, GENPOL.
* In the program this pattern has several meanings: (1) pattern = infomation
* bits, when constructing the encoding table; (2) pattern = error pattern,
* when constructing the decoding table; and (3) pattern = received vector, to
* obtain its syndrome in decoding.
*/
{
unsigned int aux = X18;
if (pattern >= X11) {
while (pattern & MASK8) {
while (!(aux & pattern))
aux = aux >> 1;
pattern ^= (aux / X11) * GENPOL;
}
}
return pattern;
}
unsigned char CGolay2087::decode(const unsigned char* data)
{
assert(data != NULL);
unsigned int code = (data[0U] << 11) + (data[1U] << 3) + (data[2U] >> 5);
unsigned int syndrome = getSyndrome1987(code);
unsigned int error_pattern = DECODING_TABLE_1987[syndrome];
if (error_pattern != 0x00U)
code ^= error_pattern;
return code >> 11;
}
void CGolay2087::encode(unsigned char* data)
{
assert(data != NULL);
unsigned int value = data[0U];
unsigned int cksum = ENCODING_TABLE_2087[value];
data[1U] = cksum & 0xFFU;
data[2U] = cksum >> 8;
}

@ -0,0 +1,32 @@
/*
* Copyright (C) 2015 by Jonathan Naylor G4KLX
*
* 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.
*/
#ifndef Golay2087_H
#define Golay2087_H
class CGolay2087 {
public:
static void encode(unsigned char* data);
static unsigned char decode(const unsigned char* data);
private:
static unsigned int getSyndrome1987(unsigned int pattern);
};
#endif

@ -0,0 +1,349 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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 "chamming.h"
#include <cstdio>
#include <cassert>
// Hamming (15,11,3) check a boolean data array
bool CHamming::decode15113_1(bool* d)
{
assert(d != NULL);
// Calculate the parity it should have
bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6];
bool c1 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[7] ^ d[8] ^ d[9];
bool c2 = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[8] ^ d[10];
bool c3 = d[0] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[9] ^ d[10];
unsigned char n = 0U;
n |= (c0 != d[11]) ? 0x01U : 0x00U;
n |= (c1 != d[12]) ? 0x02U : 0x00U;
n |= (c2 != d[13]) ? 0x04U : 0x00U;
n |= (c3 != d[14]) ? 0x08U : 0x00U;
switch (n)
{
// Parity bit errors
case 0x01U: d[11] = !d[11]; return true;
case 0x02U: d[12] = !d[12]; return true;
case 0x04U: d[13] = !d[13]; return true;
case 0x08U: d[14] = !d[14]; return true;
// Data bit errors
case 0x0FU: d[0] = !d[0]; return true;
case 0x07U: d[1] = !d[1]; return true;
case 0x0BU: d[2] = !d[2]; return true;
case 0x03U: d[3] = !d[3]; return true;
case 0x0DU: d[4] = !d[4]; return true;
case 0x05U: d[5] = !d[5]; return true;
case 0x09U: d[6] = !d[6]; return true;
case 0x0EU: d[7] = !d[7]; return true;
case 0x06U: d[8] = !d[8]; return true;
case 0x0AU: d[9] = !d[9]; return true;
case 0x0CU: d[10] = !d[10]; return true;
// No bit errors
default: return false;
}
}
void CHamming::encode15113_1(bool* d)
{
assert(d != NULL);
// Calculate the checksum this row should have
d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6];
d[12] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[7] ^ d[8] ^ d[9];
d[13] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[8] ^ d[10];
d[14] = d[0] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[9] ^ d[10];
}
// Hamming (15,11,3) check a boolean data array
bool CHamming::decode15113_2(bool* d)
{
assert(d != NULL);
// Calculate the checksum this row should have
bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
bool c1 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9];
bool c2 = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10];
bool c3 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10];
unsigned char n = 0x00U;
n |= (c0 != d[11]) ? 0x01U : 0x00U;
n |= (c1 != d[12]) ? 0x02U : 0x00U;
n |= (c2 != d[13]) ? 0x04U : 0x00U;
n |= (c3 != d[14]) ? 0x08U : 0x00U;
switch (n) {
// Parity bit errors
case 0x01U: d[11] = !d[11]; return true;
case 0x02U: d[12] = !d[12]; return true;
case 0x04U: d[13] = !d[13]; return true;
case 0x08U: d[14] = !d[14]; return true;
// Data bit errors
case 0x09U: d[0] = !d[0]; return true;
case 0x0BU: d[1] = !d[1]; return true;
case 0x0FU: d[2] = !d[2]; return true;
case 0x07U: d[3] = !d[3]; return true;
case 0x0EU: d[4] = !d[4]; return true;
case 0x05U: d[5] = !d[5]; return true;
case 0x0AU: d[6] = !d[6]; return true;
case 0x0DU: d[7] = !d[7]; return true;
case 0x03U: d[8] = !d[8]; return true;
case 0x06U: d[9] = !d[9]; return true;
case 0x0CU: d[10] = !d[10]; return true;
// No bit errors
default: return false;
}
}
void CHamming::encode15113_2(bool* d)
{
assert(d != NULL);
// Calculate the checksum this row should have
d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9];
d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10];
d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10];
}
// Hamming (13,9,3) check a boolean data array
bool CHamming::decode1393(bool* d)
{
assert(d != NULL);
// Calculate the checksum this column should have
bool c0 = d[0] ^ d[1] ^ d[3] ^ d[5] ^ d[6];
bool c1 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7];
bool c2 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
bool c3 = d[0] ^ d[2] ^ d[4] ^ d[5] ^ d[8];
unsigned char n = 0x00U;
n |= (c0 != d[9]) ? 0x01U : 0x00U;
n |= (c1 != d[10]) ? 0x02U : 0x00U;
n |= (c2 != d[11]) ? 0x04U : 0x00U;
n |= (c3 != d[12]) ? 0x08U : 0x00U;
switch (n) {
// Parity bit errors
case 0x01U: d[9] = !d[9]; return true;
case 0x02U: d[10] = !d[10]; return true;
case 0x04U: d[11] = !d[11]; return true;
case 0x08U: d[12] = !d[12]; return true;
// Data bit erros
case 0x0FU: d[0] = !d[0]; return true;
case 0x07U: d[1] = !d[1]; return true;
case 0x0EU: d[2] = !d[2]; return true;
case 0x05U: d[3] = !d[3]; return true;
case 0x0AU: d[4] = !d[4]; return true;
case 0x0DU: d[5] = !d[5]; return true;
case 0x03U: d[6] = !d[6]; return true;
case 0x06U: d[7] = !d[7]; return true;
case 0x0CU: d[8] = !d[8]; return true;
// No bit errors
default: return false;
}
}
void CHamming::encode1393(bool* d)
{
assert(d != NULL);
// Calculate the checksum this column should have
d[9] = d[0] ^ d[1] ^ d[3] ^ d[5] ^ d[6];
d[10] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7];
d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
d[12] = d[0] ^ d[2] ^ d[4] ^ d[5] ^ d[8];
}
// Hamming (10,6,3) check a boolean data array
bool CHamming::decode1063(bool* d)
{
assert(d != NULL);
// Calculate the checksum this column should have
bool c0 = d[0] ^ d[1] ^ d[2] ^ d[5];
bool c1 = d[0] ^ d[1] ^ d[3] ^ d[5];
bool c2 = d[0] ^ d[2] ^ d[3] ^ d[4];
bool c3 = d[1] ^ d[2] ^ d[3] ^ d[4];
unsigned char n = 0x00U;
n |= (c0 != d[6]) ? 0x01U : 0x00U;
n |= (c1 != d[7]) ? 0x02U : 0x00U;
n |= (c2 != d[8]) ? 0x04U : 0x00U;
n |= (c3 != d[9]) ? 0x08U : 0x00U;
switch (n) {
// Parity bit errors
case 0x01U: d[6] = !d[6]; return true;
case 0x02U: d[7] = !d[7]; return true;
case 0x04U: d[8] = !d[8]; return true;
case 0x08U: d[9] = !d[9]; return true;
// Data bit erros
case 0x07U: d[0] = !d[0]; return true;
case 0x0BU: d[1] = !d[1]; return true;
case 0x0DU: d[2] = !d[2]; return true;
case 0x0EU: d[3] = !d[3]; return true;
case 0x0CU: d[4] = !d[4]; return true;
case 0x03U: d[5] = !d[5]; return true;
// No bit errors
default: return false;
}
}
void CHamming::encode1063(bool* d)
{
assert(d != NULL);
// Calculate the checksum this column should have
d[6] = d[0] ^ d[1] ^ d[2] ^ d[5];
d[7] = d[0] ^ d[1] ^ d[3] ^ d[5];
d[8] = d[0] ^ d[2] ^ d[3] ^ d[4];
d[9] = d[1] ^ d[2] ^ d[3] ^ d[4];
}
// A Hamming (16,11,4) Check
bool CHamming::decode16114(bool* d)
{
assert(d != NULL);
// Calculate the checksum this column should have
bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
bool c1 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9];
bool c2 = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10];
bool c3 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10];
bool c4 = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10];
// Compare these with the actual bits
unsigned char n = 0x00U;
n |= (c0 != d[11]) ? 0x01U : 0x00U;
n |= (c1 != d[12]) ? 0x02U : 0x00U;
n |= (c2 != d[13]) ? 0x04U : 0x00U;
n |= (c3 != d[14]) ? 0x08U : 0x00U;
n |= (c4 != d[15]) ? 0x10U : 0x00U;
switch (n) {
// Parity bit errors
case 0x01U: d[11] = !d[11]; return true;
case 0x02U: d[12] = !d[12]; return true;
case 0x04U: d[13] = !d[13]; return true;
case 0x08U: d[14] = !d[14]; return true;
case 0x10U: d[15] = !d[15]; return true;
// Data bit errors
case 0x19U: d[0] = !d[0]; return true;
case 0x0BU: d[1] = !d[1]; return true;
case 0x1FU: d[2] = !d[2]; return true;
case 0x07U: d[3] = !d[3]; return true;
case 0x0EU: d[4] = !d[4]; return true;
case 0x15U: d[5] = !d[5]; return true;
case 0x1AU: d[6] = !d[6]; return true;
case 0x0DU: d[7] = !d[7]; return true;
case 0x13U: d[8] = !d[8]; return true;
case 0x16U: d[9] = !d[9]; return true;
case 0x1CU: d[10] = !d[10]; return true;
// No bit errors
case 0x00U: return true;
// Unrecoverable errors
default: return false;
}
}
void CHamming::encode16114(bool* d)
{
assert(d != NULL);
d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9];
d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10];
d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10];
d[15] = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10];
}
// A Hamming (17,12,3) Check
bool CHamming::decode17123(bool* d)
{
assert(d != NULL);
// Calculate the checksum this column should have
bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[6] ^ d[7] ^ d[9];
bool c1 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[7] ^ d[8] ^ d[10];
bool c2 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[8] ^ d[9] ^ d[11];
bool c3 = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10];
bool c4 = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11];
// Compare these with the actual bits
unsigned char n = 0x00U;
n |= (c0 != d[12]) ? 0x01U : 0x00U;
n |= (c1 != d[13]) ? 0x02U : 0x00U;
n |= (c2 != d[14]) ? 0x04U : 0x00U;
n |= (c3 != d[15]) ? 0x08U : 0x00U;
n |= (c4 != d[16]) ? 0x10U : 0x00U;
switch (n) {
// Parity bit errors
case 0x01U: d[12] = !d[12]; return true;
case 0x02U: d[13] = !d[13]; return true;
case 0x04U: d[14] = !d[14]; return true;
case 0x08U: d[15] = !d[15]; return true;
case 0x10U: d[16] = !d[16]; return true;
// Data bit errors
case 0x1BU: d[0] = !d[0]; return true;
case 0x1FU: d[1] = !d[1]; return true;
case 0x17U: d[2] = !d[2]; return true;
case 0x07U: d[3] = !d[3]; return true;
case 0x0EU: d[4] = !d[4]; return true;
case 0x1CU: d[5] = !d[5]; return true;
case 0x11U: d[6] = !d[6]; return true;
case 0x0BU: d[7] = !d[7]; return true;
case 0x16U: d[8] = !d[8]; return true;
case 0x05U: d[9] = !d[9]; return true;
case 0x0AU: d[10] = !d[10]; return true;
case 0x14U: d[11] = !d[11]; return true;
// No bit errors
case 0x00U: return true;
// Unrecoverable errors
default: return false;
}
}
void CHamming::encode17123(bool* d)
{
assert(d != NULL);
d[12] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[6] ^ d[7] ^ d[9];
d[13] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[7] ^ d[8] ^ d[10];
d[14] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[8] ^ d[9] ^ d[11];
d[15] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10];
d[16] = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11];
}

@ -0,0 +1,43 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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.
*/
#ifndef Hamming_H
#define Hamming_H
class CHamming {
public:
static void encode15113_1(bool* d);
static bool decode15113_1(bool* d);
static void encode15113_2(bool* d);
static bool decode15113_2(bool* d);
static void encode1393(bool* d);
static bool decode1393(bool* d);
static void encode1063(bool* d);
static bool decode1063(bool* d);
static void encode16114(bool* d);
static bool decode16114(bool* d);
static void encode17123(bool* d);
static bool decode17123(bool* d);
};
#endif

@ -0,0 +1,199 @@
/*
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 "codec.h"
#include <iostream>
#include <dlfcn.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
Codec::Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) :
m_module(module),
m_hostname(hostname),
m_tx(false),
m_ttsid(0),
m_audioin(audioin),
m_audioout(audioout),
m_rxwatchdog(0),
#ifdef Q_OS_WIN
m_rxtimerint(19),
#else
m_rxtimerint(20),
#endif
m_txtimerint(19),
m_vocoder(vocoder),
m_modemport(modem),
m_modem(nullptr),
m_ambedev(nullptr),
m_hwrx(false),
m_hwtx(false),
m_ipv6(ipv6)
{
m_modeinfo.callsign = callsign;
m_modeinfo.gwid = 0;
m_modeinfo.srcid = 0;
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.vocoder_loaded = false;
#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
}
Codec::~Codec()
{
}
void Codec::in_audio_vol_changed(qreal v)
{
m_audio->set_input_volume(v);
}
void Codec::out_audio_vol_changed(qreal v)
{
m_audio->set_output_volume(v);
}
void Codec::agc_state_changed(int s)
{
qDebug() << "Codec::agc_state_changed() called s == " << s;
m_audio->set_agc(s);
}
void Codec::send_connect()
{
m_modeinfo.status = CONNECTING;
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 Codec::toggle_tx(bool tx)
{
tx ? start_tx() : stop_tx();
}
void Codec::start_tx()
{
if(m_hwtx){
m_ambedev->clear_queue();
}
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 Codec::stop_tx()
{
m_tx = false;
}
bool Codec::load_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)
QString voc = config_path + "/vocoder_plugin." + QSysInfo::productType() + "." + QSysInfo::currentCpuArchitecture();
#else
QString voc = config_path + "/vocoder_plugin." + QSysInfo::kernelType() + "." + QSysInfo::currentCpuArchitecture();
#endif
//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;
}
void Codec::deleteLater()
{
if(m_modeinfo.status == CONNECTED_RW){
m_udp->disconnect();
m_ping_timer->stop();
send_disconnect();
delete m_audio;
if(m_hwtx){
delete m_ambedev;
}
if(m_modem){
delete m_modem;
}
}
m_modeinfo.count = 0;
QObject::deleteLater();
}

@ -0,0 +1,206 @@
/*
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/>.
*/
#ifndef CODEC_H
#define CODEC_H
#include <QObject>
#include <QtNetwork>
#ifdef USE_FLITE
#include <flite/flite.h>
#endif
#include <imbe_vocoder_api.h>
#include "vocoder_plugin.h"
#include "audioengine.h"
#include "serialambe.h"
#include "serialmodem.h"
class Codec : public QObject
{
Q_OBJECT
public:
Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout);
~Codec();
void set_modem_flags(bool rxInvert, bool txInvert, bool pttInvert, bool useCOSAsLockout, bool duplex) { m_rxInvert = rxInvert; m_txInvert = txInvert; m_pttInvert = pttInvert; m_useCOSAsLockout = useCOSAsLockout; m_duplex = duplex; }
void set_modem_params(uint32_t rxfreq, uint32_t txfreq, uint32_t txDelay, float rxLevel, float rfLevel, uint32_t ysfTXHang, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float pocsagTXLevel)
{
m_rxfreq = rxfreq;
m_txfreq = txfreq;
m_txDelay = txDelay;
m_rxLevel = rxLevel;
m_rfLevel = rfLevel;
m_ysfTXHang = ysfTXHang;
m_cwIdTXLevel = cwIdTXLevel;
m_dstarTXLevel = dstarTXLevel;
m_dmrTXLevel = dmrTXLevel;
m_ysfTXLevel = ysfTXLevel;
m_p25TXLevel = p25TXLevel;
m_nxdnTXLevel = nxdnTXLevel;
m_pocsagTXLevel = pocsagTXLevel;
}
bool get_hwrx() { return m_hwrx; }
bool get_hwtx() { return m_hwtx; }
void set_hostname(std::string);
void set_callsign(std::string);
void set_input_src(uint8_t s, QString t) { m_ttsid = s; m_ttstext = t; }
struct MODEINFO {
qint64 ts;
int status;
int stream_state;
QString callsign;
QString gw;
QString gw2;
QString src;
QString dst;
QString usertxt;
QString netmsg;
uint32_t gwid;
uint32_t srcid;
uint32_t dstid;
QString host;
int port;
bool path;
char type;
uint16_t frame_number;
uint8_t frame_total;
int count;
uint32_t streamid;
bool mode;
bool vocoder_loaded;
} m_modeinfo;
enum{
DISCONNECTED,
CLOSED,
CONNECTING,
DMR_AUTH,
DMR_CONF,
DMR_OPTS,
CONNECTED_RW,
CONNECTED_RO
};
enum{
STREAM_NEW,
STREAMING,
STREAM_END,
STREAM_LOST,
STREAM_IDLE,
TRANSMITTING,
TRANSMITTING_MODEM,
STREAM_UNKNOWN
};
signals:
void update(Codec::MODEINFO);
void update_output_level(unsigned short);
protected slots:
virtual void send_disconnect(){}
virtual void hostname_lookup(QHostInfo){}
void send_connect();
void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; }
void start_tx();
void stop_tx();
void toggle_tx(bool);
void deleteLater();
void in_audio_vol_changed(qreal);
void out_audio_vol_changed(qreal);
bool load_vocoder_plugin();
void swrx_state_changed(int s) {m_hwrx = !s; }
void swtx_state_changed(int s) {m_hwtx = !s; }
void agc_state_changed(int s);
void mycall_changed(QString mc) { m_txmycall = mc; }
void urcall_changed(QString uc) { m_txurcall = uc; }
void rptr1_changed(QString r1) { m_txrptr1 = r1; }
void rptr2_changed(QString r2) { m_txrptr2 = r2; }
void module_changed(char m) { m_module = m; m_modeinfo.streamid = 0; qDebug() << "Codec::module_changed() m == " << m; }
protected:
QUdpSocket *m_udp = nullptr;
QHostAddress m_address;
char m_module;
QString m_hostname;
bool m_tx;
uint16_t m_txcnt;
uint16_t m_ttscnt;
uint8_t m_ttsid;
QString m_ttstext;
QString m_txmycall;
QString m_txurcall;
QString m_txrptr1;
QString m_txrptr2;
#ifdef USE_FLITE
cst_voice *voice_slt;
cst_voice *voice_kal;
cst_voice *voice_awb;
cst_wave *tts_audio;
#endif
QTimer *m_ping_timer;
QTimer *m_txtimer;
QTimer *m_rxtimer;
AudioEngine *m_audio;
QString m_audioin;
QString m_audioout;
uint32_t m_rxwatchdog;
uint8_t m_rxtimerint;
uint8_t m_txtimerint;
QQueue<uint8_t> m_rxcodecq;
QQueue<uint8_t> m_txcodecq;
QQueue<uint8_t> m_rxmodemq;
imbe_vocoder vocoder;
Vocoder *m_mbevocoder;
QString m_vocoder;
QString m_modemport;
SerialModem *m_modem;
SerialAMBE *m_ambedev;
bool m_hwrx;
bool m_hwtx;
bool m_ipv6;
uint32_t m_rxfreq;
uint32_t m_txfreq;
uint32_t m_dmrColorCode;
bool m_ysfLoDev;
uint32_t m_ysfTXHang;
uint32_t m_p25TXHang;
uint32_t m_nxdnTXHang;
bool m_duplex;
bool m_rxInvert;
bool m_txInvert;
bool m_pttInvert;
uint32_t m_txDelay;
uint32_t m_dmrDelay;
float m_rxLevel;
float m_rfLevel;
float m_cwIdTXLevel;
float m_dstarTXLevel;
float m_dmrTXLevel;
float m_ysfTXLevel;
float m_p25TXLevel;
float m_nxdnTXLevel;
float m_pocsagTXLevel;
float m_fmTXLevel;
bool m_debug;
bool m_useCOSAsLockout;
bool m_dstarEnabled;
bool m_dmrEnabled;
bool m_ysfEnabled;
bool m_p25Enabled;
bool m_nxdnEnabled;
bool m_pocsagEnabled;
bool m_fmEnabled;
int m_rxDCOffset;
int m_txDCOffset;
};
#endif // CODEC_H

@ -0,0 +1,964 @@
/*
* This intermediary file and the files that used to create it are under
* The LGPL. See the file COPYING.
*/
#include "defines.h"
/* codebook/lsp1.txt */
static float codes00[] =
{
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600
};
/* codebook/lsp2.txt */
static float codes01[] =
{
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700
};
/* codebook/lsp3.txt */
static float codes02[] =
{
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250
};
/* codebook/lsp4.txt */
static float codes03[] =
{
700,
800,
900,
1000,
1100,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200
};
/* codebook/lsp5.txt */
static float codes04[] =
{
950,
1050,
1150,
1250,
1350,
1450,
1550,
1650,
1750,
1850,
1950,
2050,
2150,
2250,
2350,
2450
};
/* codebook/lsp6.txt */
static float codes05[] =
{
1100,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200,
2300,
2400,
2500,
2600
};
/* codebook/lsp7.txt */
static float codes06[] =
{
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200,
2300,
2400,
2500,
2600,
2700,
2800,
2900,
3000
};
/* codebook/lsp8.txt */
static float codes07[] =
{
2300,
2400,
2500,
2600,
2700,
2800,
2900,
3000
};
/* codebook/lsp9.txt */
static float codes08[] =
{
2500,
2600,
2700,
2800,
2900,
3000,
3100,
3200
};
/* codebook/lsp10.txt */
static float codes09[] =
{
2900,
3100,
3300,
3500
};
const struct lsp_codebook lsp_cb[] =
{
/* codebook/lsp1.txt */
{
1,
4,
16,
codes00
},
/* codebook/lsp2.txt */
{
1,
4,
16,
codes01
},
/* codebook/lsp3.txt */
{
1,
4,
16,
codes02
},
/* codebook/lsp4.txt */
{
1,
4,
16,
codes03
},
/* codebook/lsp5.txt */
{
1,
4,
16,
codes04
},
/* codebook/lsp6.txt */
{
1,
4,
16,
codes05
},
/* codebook/lsp7.txt */
{
1,
4,
16,
codes06
},
/* codebook/lsp8.txt */
{
1,
3,
8,
codes07
},
/* codebook/lsp9.txt */
{
1,
3,
8,
codes08
},
/* codebook/lsp10.txt */
{
1,
2,
4,
codes09
},
{ 0, 0, 0, 0 }
};
/* codebook/dlsp1.txt */
static float codes10[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp2.txt */
static float codes11[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp3.txt */
static float codes12[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp4.txt */
static float codes13[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400
};
/* codebook/dlsp5.txt */
static float codes14[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400
};
/* codebook/dlsp6.txt */
static float codes15[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400
};
/* codebook/dlsp7.txt */
static float codes16[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp8.txt */
static float codes17[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp9.txt */
static float codes18[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp10.txt */
static float codes19[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
const struct lsp_codebook lsp_cbd[] =
{
/* codebook/dlsp1.txt */
{
1,
5,
32,
codes10
},
/* codebook/dlsp2.txt */
{
1,
5,
32,
codes11
},
/* codebook/dlsp3.txt */
{
1,
5,
32,
codes12
},
/* codebook/dlsp4.txt */
{
1,
5,
32,
codes13
},
/* codebook/dlsp5.txt */
{
1,
5,
32,
codes14
},
/* codebook/dlsp6.txt */
{
1,
5,
32,
codes15
},
/* codebook/dlsp7.txt */
{
1,
5,
32,
codes16
},
/* codebook/dlsp8.txt */
{
1,
5,
32,
codes17
},
/* codebook/dlsp9.txt */
{
1,
5,
32,
codes18
},
/* codebook/dlsp10.txt */
{
1,
5,
32,
codes19
},
{ 0, 0, 0, 0 }
};
/* codebook/gecb.txt */
static float codes30[] =
{
2.71, 12.0184,
0.04675, -2.73881,
0.120993, 8.38895,
-1.58028, -0.892307,
1.19307, -1.91561,
0.187101, -3.27679,
0.332251, -7.66455,
-1.47944, 31.2461,
1.52761, 27.7095,
-0.524379, 5.25012,
0.55333, 7.4388,
-0.843451, -1.95299,
2.26389, 8.61029,
0.143143, 2.36549,
0.616506, 1.28427,
-1.71133, 22.0967,
1.00813, 17.3965,
-0.106718, 1.41891,
-0.136246, 14.2736,
-1.70909, -20.5319,
1.65787, -3.39107,
0.138049, -4.95785,
0.536729, -1.94375,
0.196307, 36.8519,
1.27248, 22.5565,
-0.670219, -1.90604,
0.382092, 6.40113,
-0.756911, -4.90102,
1.82931, 4.6138,
0.318794, 0.73683,
0.612815, -2.07505,
-0.410151, 24.7871,
1.77602, 13.1909,
0.106457, -0.104492,
0.192206, 10.1838,
-1.82442, -7.71565,
0.931346, 4.34835,
0.308813, -4.086,
0.397143, -11.8089,
-0.048715, 41.2273,
0.877342, 35.8503,
-0.759794, 0.476634,
0.978593, 7.67467,
-1.19506, 3.03883,
2.63989, -3.41106,
0.191127, 3.60351,
0.402932, 1.0843,
-2.15202, 18.1076,
1.5468, 8.32271,
-0.143089, -4.07592,
-0.150142, 5.86674,
-1.40844, -3.2507,
1.56615, -10.4132,
0.178171, -10.2267,
0.362164, -0.028556,
-0.070125, 24.3907,
0.594752, 17.4828,
-0.28698, -6.90407,
0.464818, 10.2055,
-1.00684, -14.3572,
2.32957, -3.69161,
0.335745, 2.40714,
1.01966, -3.15565,
-1.25945, 7.9919,
2.38369, 19.6806,
-0.094947, -2.41374,
0.20933, 6.66477,
-2.22103, 1.37986,
1.29239, 2.04633,
0.243626, -0.890741,
0.428773, -7.19366,
-1.11374, 41.3414,
2.6098, 31.1405,
-0.446468, 2.53419,
0.490104, 4.62757,
-1.11723, -3.24174,
1.79156, 8.41493,
0.156012, 0.183336,
0.532447, 3.15455,
-0.764484, 18.514,
0.952395, 11.7713,
-0.332567, 0.346987,
0.202165, 14.7168,
-2.12924, -15.559,
1.35358, -1.92679,
-0.010963, -16.3364,
0.399053, -2.79057,
0.750657, 31.1483,
0.655743, 24.4819,
-0.45321, -0.735879,
0.2869, 6.5467,
-0.715673, -12.3578,
1.54849, 3.87217,
0.271874, 0.802339,
0.502073, -4.85485,
-0.497037, 17.7619,
1.19116, 13.9544,
0.01563, 1.33157,
0.341867, 8.93537,
-2.31601, -5.39506,
0.75861, 1.9645,
0.24132, -3.23769,
0.267151, -11.2344,
-0.273126, 32.6248,
1.75352, 40.432,
-0.784011, 3.04576,
0.705987, 5.66118,
-1.3864, 1.35356,
2.37646, 1.67485,
0.242973, 4.73218,
0.491227, 0.354061,
-1.60676, 8.65895,
1.16711, 5.9871,
-0.137601, -12.0417,
-0.251375, 10.3972,
-1.43151, -8.90411,
0.98828, -13.209,
0.261484, -6.35497,
0.395932, -0.702529,
0.283704, 26.8996,
0.420959, 15.4418,
-0.355804, -13.7278,
0.527372, 12.3985,
-1.16956, -15.9985,
1.90669, -5.81605,
0.354492, 3.85157,
0.82576, -4.16264,
-0.49019, 13.0572,
2.25577, 13.5264,
-0.004956, -3.23713,
0.026709, 7.86645,
-1.81037, -0.451183,
1.08383, -0.18362,
0.135836, -2.26658,
0.375812, -5.51225,
-1.96644, 38.6829,
1.97799, 24.5655,
-0.704656, 6.35881,
0.480786, 7.05175,
-0.976417, -2.42273,
2.50215, 6.75935,
0.083588, 3.2588,
0.543629, 0.910013,
-1.23196, 23.0915,
0.785492, 14.807,
-0.213554, 1.688,
0.004748, 18.1718,
-1.54719, -16.1168,
1.50104, -3.28114,
0.080133, -4.63472,
0.476592, -2.18093,
0.44247, 40.304,
1.07277, 27.592,
-0.594738, -4.16681,
0.42248, 7.61609,
-0.927521, -7.27441,
1.99162, 1.29636,
0.291307, 2.39878,
0.721081, -1.95062,
-0.804256, 24.9295,
1.64839, 19.1197,
0.060852, -0.590639,
0.266085, 9.10325,
-1.9574, -2.88461,
1.11693, 2.6724,
0.35458, -2.74854,
0.330733, -14.1561,
-0.527851, 39.5756,
0.991152, 43.195,
-0.589619, 1.26919,
0.787401, 8.73071,
-1.0138, 1.02507,
2.8254, 1.89538,
0.24089, 2.74557,
0.427195, 2.54446,
-1.95311, 12.244,
1.44862, 12.0607,
-0.210492, -3.37906,
-0.056713, 10.204,
-1.65237, -5.10274,
1.29475, -12.2708,
0.111608, -8.67592,
0.326634, -1.16763,
0.021781, 31.1258,
0.455335, 21.4684,
-0.37544, -3.37121,
0.39362, 11.302,
-0.851456, -19.4149,
2.10703, -2.22886,
0.373233, 1.92406,
0.884438, -1.72058,
-0.975127, 9.84013,
2.0033, 17.3954,
-0.036915, -1.11137,
0.148456, 5.39997,
-1.91441, 4.77382,
1.44791, 0.537122,
0.194979, -1.03818,
0.495771, -9.95502,
-1.05899, 32.9471,
2.01122, 32.4544,
-0.30965, 4.71911,
0.436082, 4.63552,
-1.23711, -1.25428,
2.02274, 9.42834,
0.190342, 1.46077,
0.479017, 2.48479,
-1.07848, 16.2217,
1.20764, 9.65421,
-0.258087, -1.67236,
0.071852, 13.416,
-1.87723, -16.072,
1.28957, -4.87118,
0.067713, -13.4427,
0.435551, -4.1655,
0.46614, 30.5895,
0.904895, 21.598,
-0.518369, -2.53205,
0.337363, 5.63726,
-0.554975, -17.4005,
1.69188, 1.14574,
0.227934, 0.889297,
0.587303, -5.72973,
-0.262133, 18.6666,
1.39505, 17.0029,
-0.01909, 4.30838,
0.304235, 12.6699,
-2.07406, -6.46084,
0.920546, 1.21296,
0.284927, -1.78547,
0.209724, -16.024,
-0.636067, 31.5768,
1.34989, 34.6775,
-0.971625, 5.30086,
0.590249, 4.44971,
-1.56787, 3.60239,
2.1455, 4.51666,
0.296022, 4.12017,
0.445299, 0.868772,
-1.44193, 14.1284,
1.35575, 6.0074,
-0.012814, -7.49657,
-0.43, 8.50012,
-1.20469, -7.11326,
1.10102, -6.83682,
0.196463, -6.234,
0.436747, -1.12979,
0.141052, 22.8549,
0.290821, 18.8114,
-0.529536, -7.73251,
0.63428, 10.7898,
-1.33472, -20.3258,
1.81564, -1.90332,
0.394778, 3.79758,
0.732682, -8.18382,
-0.741244, 11.7683
};
const struct lsp_codebook ge_cb[] =
{
/* codebook/gecb.txt */
{
2,
8,
256,
codes30
},
{ 0, 0, 0, 0 }
};

File diff suppressed because it is too large Load Diff

@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------*\
FILE........: codec2.h
AUTHOR......: David Rowe
DATE CREATED: 21 August 2010
Codec 2 fully quantised encoder and decoder functions. If you want use
Codec 2, these are the functions you need to call.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2010 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CODEC2__
#define __CODEC2__
#include <complex>
#include "codec2_internal.h"
#include "defines.h"
#include "kiss_fft.h"
#include "nlp.h"
#include "quantise.h"
#define CODEC2_MODE_3200 0
#define CODEC2_MODE_1600 2
#ifndef CODEC2_MODE_EN_DEFAULT
#define CODEC2_MODE_EN_DEFAULT 1
#endif
#define CODEC2_RAND_MAX 32767
class CCodec2
{
public:
CCodec2(bool is_3200);
~CCodec2();
void codec2_encode(unsigned char *bits, const short *speech_in);
void codec2_decode(short *speech_out, const unsigned char *bits);
void codec2_set_mode(bool);
bool codec2_get_mode() {return (c2.mode == 3200); };
int codec2_samples_per_frame();
int codec2_bits_per_frame();
void set_decode_gain(float g){ m_decode_gain = g; }
private:
// merged from other files
void sample_phase(MODEL *model, std::complex<float> filter_phase[], std::complex<float> A[]);
void phase_synth_zero_order(int n_samp, MODEL *model, float *ex_phase, std::complex<float> filter_phase[]);
void postfilter(MODEL *model, float *bg_est);
C2CONST c2const_create(int Fs, float framelength_ms);
void make_analysis_window(C2CONST *c2const, FFT_STATE *fft_fwd_cfg, float w[], float W[]);
void dft_speech(C2CONST *c2const, FFT_STATE &fft_fwd_cfg, std::complex<float> Sw[], float Sn[], float w[]);
void two_stage_pitch_refinement(C2CONST *c2const, MODEL *model, std::complex<float> Sw[]);
void estimate_amplitudes(MODEL *model, std::complex<float> Sw[], int est_phase);
float est_voicing_mbe(C2CONST *c2const, MODEL *model, std::complex<float> Sw[], float W[]);
void make_synthesis_window(C2CONST *c2const, float Pn[]);
void synthesise(int n_samp, FFTR_STATE *fftr_inv_cfg, float Sn_[], MODEL *model, float Pn[], int shift);
int codec2_rand(void);
void hs_pitch_refinement(MODEL *model, std::complex<float> Sw[], float pmin, float pmax, float pstep);
void interp_Wo(MODEL *interp, MODEL *prev, MODEL *next, float Wo_min);
void interp_Wo2(MODEL *interp, MODEL *prev, MODEL *next, float weight, float Wo_min);
float interp_energy(float prev, float next);
void interpolate_lsp_ver2(float interp[], float prev[], float next[], float weight, int order);
void analyse_one_frame(MODEL *model, const short *speech);
void synthesise_one_frame(short speech[], MODEL *model, std::complex<float> Aw[], float gain);
void codec2_encode_3200(unsigned char *bits, const short *speech);
void codec2_encode_1600(unsigned char *bits, const short *speech);
void codec2_decode_3200(short *speech, const unsigned char *bits);
void codec2_decode_1600(short *speech, const unsigned char *bits);
void ear_protection(float in_out[], int n);
void lsp_to_lpc(float *freq, float *ak, int lpcrdr);
void (CCodec2::*encode)(unsigned char *bits, const short *speech);
void (CCodec2::*decode)(short *speech, const unsigned char *bits);
Cnlp nlp;
CQuantize qt;
CODEC2 c2;
float m_decode_gain;
};
#endif

@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------*\
FILE........: codec2_internal.h
AUTHOR......: David Rowe
DATE CREATED: April 16 2012
Header file for Codec2 internal states, exposed via this header
file to assist in testing.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2012 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CODEC2_INTERNAL__
#define __CODEC2_INTERNAL__
#include "kiss_fft.h"
using CODEC2 = struct codec2_tag {
int mode;
int Fs;
int n_samp;
int m_pitch;
int gray; /* non-zero for gray encoding */
int lpc_pf; /* LPC post filter on */
int bass_boost; /* LPC post filter bass boost */
int smoothing; /* enable smoothing for channels with errors */
float ex_phase; /* excitation model phase track */
float bg_est; /* background noise estimate for post filter */
float prev_f0_enc; /* previous frame's f0 estimate */
float prev_e_dec; /* previous frame's LPC energy */
float beta; /* LPC post filter parameters */
float gamma;
float xq_enc[2]; /* joint pitch and energy VQ states */
float xq_dec[2];
float W[FFT_ENC]; /* DFT of w[] */
float hpf_states[2]; /* high pass filter states */
float prev_lsps_dec[LPC_ORD]; /* previous frame's LSPs */
float *softdec; /* optional soft decn bits from demod */
MODEL prev_model_dec; /* previous frame's model parameters */
C2CONST c2const;
FFT_STATE fft_fwd_cfg; /* forward FFT config */
FFTR_STATE fftr_fwd_cfg; /* forward real FFT config */
FFTR_STATE fftr_inv_cfg; /* inverse FFT config */
std::vector<float> w; /* [m_pitch] time domain hamming window */
std::vector<float> Pn; /* [2*n_samp] trapezoidal synthesis window */
std::vector<float> Sn; /* [m_pitch] input speech */
std::vector<float> Sn_; /* [2*n_samp] synthesised output speech */
std::vector<float> bpf_buf; /* buffer for band pass filter */
};
#endif

@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------*\
FILE........: defines.h
AUTHOR......: David Rowe
DATE CREATED: 23/4/93
Defines and structures used throughout the codec.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __DEFINES__
#define __DEFINES__
#include <complex>
#include <vector>
/*---------------------------------------------------------------------------*\
DEFINES
\*---------------------------------------------------------------------------*/
/* General defines */
#define N_S 0.01 /* internal proc frame length in secs */
#define TW_S 0.005 /* trapezoidal synth window overlap */
#define MAX_AMP 160 /* maximum number of harmonics */
#ifndef PI
#define PI 3.141592654 /* mathematical constant */
#endif
#define TWO_PI 6.283185307 /* mathematical constant */
#define MAX_STR 2048 /* maximum string size */
#define FFT_ENC 512 /* size of FFT used for encoder */
#define FFT_DEC 512 /* size of FFT used in decoder */
#define V_THRESH 6.0 /* voicing threshold in dB */
#define LPC_ORD 10 /* LPC order */
#define LPC_ORD_LOW 6 /* LPC order for lower rates */
/* Pitch estimation defines */
#define M_PITCH_S 0.0400 /* pitch analysis window in s */
#define P_MIN_S 0.0025 /* minimum pitch period in s */
#define P_MAX_S 0.0200 /* maximum pitch period in s */
#define MAXFACTORS 32 // e.g. an fft of length 128 has 4 factors
// as far as kissfft is concerned 4*4*4*2
/*---------------------------------------------------------------------------*\
TYPEDEFS
\*---------------------------------------------------------------------------*/
/* Structure to hold constants calculated at run time based on sample rate */
using C2CONST = struct c2const_tag
{
int Fs; /* sample rate of this instance */
int n_samp; /* number of samples per 10ms frame at Fs */
int max_amp; /* maximum number of harmonics */
int m_pitch; /* pitch estimation window size in samples */
int p_min; /* minimum pitch period in samples */
int p_max; /* maximum pitch period in samples */
float Wo_min;
float Wo_max;
int nw; /* analysis window size in samples */
int tw; /* trapezoidal synthesis window overlap */
};
/* Structure to hold model parameters for one frame */
using MODEL = struct model_tag
{
float Wo; /* fundamental frequency estimate in radians */
int L; /* number of harmonics */
float A[MAX_AMP+1]; /* amplitiude of each harmonic */
float phi[MAX_AMP+1]; /* phase of each harmonic */
int voiced; /* non-zero if this frame is voiced */
};
/* describes each codebook */
struct lsp_codebook
{
int k; /* dimension of vector */
int log2m; /* number of bits in m */
int m; /* elements in codebook */
float *cb; /* The elements */
};
using FFT_STATE = struct fft_state_tag
{
int nfft;
bool inverse;
int factors[2*MAXFACTORS];
std::vector<std::complex<float>> twiddles;
};
using FFTR_STATE = struct fftr_state_tag
{
FFT_STATE substate;
std::vector<std::complex<float>> tmpbuf;
std::vector<std::complex<float>> super_twiddles;
};
extern const struct lsp_codebook lsp_cb[];
extern const struct lsp_codebook lsp_cbd[];
extern const struct lsp_codebook ge_cb[];
inline float exp10f(float val)
{
return pow(10.0, val);
}
#endif

@ -0,0 +1,435 @@
/*
Copyright (c) 2003-2010, Mark Borgerding
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cstring>
#include <cassert>
#include "defines.h"
#include "kiss_fft.h"
void CKissFFT::kf_bfly2(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m)
{
std::complex<float> *Fout2;
std::complex<float> *tw1 = st.twiddles.data();
std::complex<float> t;
Fout2 = Fout + m;
do
{
t = *Fout2 * *tw1;
tw1 += fstride;
*Fout2 = *Fout - t;
*Fout += t;
++Fout2;
++Fout;
}
while (--m);
}
void CKissFFT::kf_bfly3(std::complex<float> * Fout, const size_t fstride, FFT_STATE &st, int m)
{
const size_t m2 = 2 * m;
std::complex<float> *tw1,*tw2;
std::complex<float> scratch[5];
std::complex<float> epi3;
epi3 = st.twiddles[fstride*m];
tw1 = tw2 = st.twiddles.data();
do
{
scratch[1] = Fout[m] * *tw1;
scratch[2] = Fout[m2] * *tw2;
scratch[3] = scratch[1] + scratch[2];
scratch[0] = scratch[1] - scratch[2];
tw1 += fstride;
tw2 += fstride*2;
Fout[m] = *Fout - (0.5f * scratch[3]);
scratch[0] *= epi3.imag();
*Fout += scratch[3];
Fout[m2].real(Fout[m].real() + scratch[0].imag());
Fout[m2].imag(Fout[m].imag() - scratch[0].real());
Fout[m].real(Fout[m].real() - scratch[0].imag());
Fout[m].imag(Fout[m].imag() + scratch[0].real());
++Fout;
}
while(--m);
}
void CKissFFT::kf_bfly4(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m)
{
std::complex<float> *tw1,*tw2,*tw3;
std::complex<float> scratch[6];
int k = m;
const int m2 = 2 * m;
const int m3 = 3 * m;
tw3 = tw2 = tw1 = st.twiddles.data();
do
{
scratch[0] = Fout[m] * *tw1;
scratch[1] = Fout[m2] * *tw2;
scratch[2] = Fout[m3] * *tw3;
scratch[5] = *Fout - scratch[1];
*Fout += scratch[1];
scratch[3] = scratch[0] + scratch[2];
scratch[4] = scratch[0] - scratch[2];
Fout[m2] = *Fout - scratch[3];
tw1 += fstride;
tw2 += fstride*2;
tw3 += fstride*3;
*Fout += scratch[3];
if(st.inverse)
{
Fout[m].real(scratch[5].real() - scratch[4].imag());
Fout[m].imag(scratch[5].imag() + scratch[4].real());
Fout[m3].real(scratch[5].real() + scratch[4].imag());
Fout[m3].imag(scratch[5].imag() - scratch[4].real());
}
else
{
Fout[m].real(scratch[5].real() + scratch[4].imag());
Fout[m].imag(scratch[5].imag() - scratch[4].real());
Fout[m3].real(scratch[5].real() - scratch[4].imag());
Fout[m3].imag(scratch[5].imag() + scratch[4].real());
}
++Fout;
}
while(--k);
}
void CKissFFT::kf_bfly5(std::complex<float> * Fout, const size_t fstride, FFT_STATE &st, int m)
{
std::complex<float> scratch[13];
std::complex<float> *twiddles = st.twiddles.data();
auto ya = twiddles[fstride*m];
auto yb = twiddles[fstride*2*m];
auto Fout0 = Fout;
auto Fout1 = Fout0 + m;
auto Fout2 = Fout0 + 2 * m;
auto Fout3 = Fout0 + 3 * m;
auto Fout4 = Fout0 + 4 * m;
auto tw = st.twiddles.data();
for (int u=0; u<m; ++u)
{
scratch[0] = *Fout0;
scratch[1] = *Fout1 * tw[u*fstride];
scratch[2] = *Fout2 * tw[2*u*fstride];
scratch[3] = *Fout3 * tw[3*u*fstride];
scratch[4] = *Fout4 * tw[4*u*fstride];
scratch[7] = scratch[1] + scratch[4];
scratch[10] = scratch[1] - scratch[4];
scratch[8] = scratch[2] + scratch[3];
scratch[9] = scratch[2] - scratch[3];
*Fout0 += scratch[7] + scratch[8];
scratch[5] = scratch[0] + (scratch[7] * ya.real()) + (scratch[8] * yb.real());
scratch[6].real( (scratch[10].imag() * ya.imag()) + (scratch[9].imag() * yb.imag()));
scratch[6].imag(-(scratch[10].real() * ya.imag()) - (scratch[9].real() * yb.imag()));
*Fout1 = scratch[5] - scratch[6];
*Fout4 = scratch[5] + scratch[6];
scratch[11] = scratch[0] + (scratch[7] * yb.real()) + (scratch[8] * ya.real());
scratch[12].real(-(scratch[10].imag() * yb.imag()) + (scratch[9].imag() * ya.imag()));
scratch[12].imag( (scratch[10].real() * yb.imag()) - (scratch[9].real() * ya.imag()));
*Fout2 = scratch[11] + scratch[12];
*Fout3 = scratch[11] - scratch[12];
++Fout0;
++Fout1;
++Fout2;
++Fout3;
++Fout4;
}
}
/* perform the butterfly for one stage of a mixed radix FFT */
void CKissFFT::kf_bfly_generic(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m, int p)
{
auto twiddles = st.twiddles.data();
std::complex<float> t;
int Norig = st.nfft;
std::vector<std::complex<float>> scratch(p);
for (int u=0; u<m; ++u)
{
int k = u;
for (int q1=0 ; q1<p ; ++q1)
{
scratch[q1] = Fout[k];
k += m;
}
k = u;
for (int q1=0 ; q1<p ; ++q1)
{
int twidx = 0;
Fout[k] = scratch[0];
for (int q=1; q<p; ++q)
{
twidx += fstride * k;
if (twidx >= Norig) twidx-=Norig;
t = scratch[q] * twiddles[twidx];
Fout[k] += t;
}
k += m;
}
}
scratch.clear();
}
void CKissFFT::kf_work(std::complex<float> *Fout, const std::complex<float> *f, const size_t fstride, int in_stride, int *factors, FFT_STATE &st)
{
auto Fout_beg = Fout;
const int p = *factors++; /* the radix */
const int m = *factors++; /* stage's fft length/p */
const std::complex<float> *Fout_end = Fout + p*m;
if (m==1)
{
do
{
*Fout = *f;
f += fstride*in_stride;
}
while( ++Fout != Fout_end );
}
else
{
do
{
// recursive call:
// DFT of size m*p performed by doing
// p instances of smaller DFTs of size m,
// each one takes a decimated version of the input
kf_work( Fout, f, fstride*p, in_stride, factors, st);
f += fstride*in_stride;
}
while( (Fout += m) != Fout_end );
}
Fout=Fout_beg;
// recombine the p smaller DFTs
switch (p)
{
case 2:
kf_bfly2(Fout,fstride,st,m);
break;
case 3:
kf_bfly3(Fout,fstride,st,m);
break;
case 4:
kf_bfly4(Fout,fstride,st,m);
break;
case 5:
kf_bfly5(Fout,fstride,st,m);
break;
default:
kf_bfly_generic(Fout,fstride,st,m,p);
break;
}
}
/* facbuf is populated by p1,m1,p2,m2, ...
where
p[i] * m[i] = m[i-1]
m0 = n */
void CKissFFT::kf_factor(int n,int * facbuf)
{
int p=4;
double floor_sqrt;
floor_sqrt = floorf( sqrtf((double)n) );
/*factor out powers of 4, powers of 2, then any remaining primes */
do
{
while (n % p)
{
switch (p)
{
case 4:
p = 2;
break;
case 2:
p = 3;
break;
default:
p += 2;
break;
}
if (p > floor_sqrt)
p = n; /* no more factors, skip to end */
}
n /= p;
*facbuf++ = p;
*facbuf++ = n;
}
while (n > 1);
}
void CKissFFT::fft_alloc(FFT_STATE &state, const int nfft, bool inverse_fft)
{
state.twiddles.resize(nfft);
state.nfft = nfft;
state.inverse = inverse_fft;
for (int i=0; i<nfft; ++i)
{
const double pi=3.141592653589793238462643383279502884197169399375105820974944;
double phase = -2.0 * pi * i / nfft;
if (state.inverse)
phase *= -1.0;
state.twiddles[i] = std::polar(1.0f, float(phase));
}
kf_factor(nfft, state.factors);
}
void CKissFFT::fft_stride(FFT_STATE &st, const std::complex<float> *fin, std::complex<float> *fout, int in_stride)
{
if (fin == fout)
{
//NOTE: this is not really an in-place FFT algorithm.
//It just performs an out-of-place FFT into a temp buffer
std::vector<std::complex<float>> tmpbuf(st.nfft);
kf_work(tmpbuf.data(), fin, true, in_stride, st.factors, st);
memcpy(fout, tmpbuf.data(), sizeof(std::complex<float>)*st.nfft);
tmpbuf.clear();
}
else
{
kf_work(fout, fin, 1, in_stride, st.factors, st);
}
}
void CKissFFT::fft(FFT_STATE &cfg, const std::complex<float> *fin, std::complex<float> *fout)
{
fft_stride(cfg, fin, fout, 1);
}
int CKissFFT::fft_next_fast_size(int n)
{
while(1)
{
int m = n;
while ( (m % 2) == 0 ) m /= 2;
while ( (m % 3) == 0 ) m /= 3;
while ( (m % 5) == 0 ) m /= 5;
if (m <= 1)
break; /* n is completely factorable by twos, threes, and fives */
n++;
}
return n;
}
void CKissFFT::fftr_alloc(FFTR_STATE &st, int nfft, const bool inverse_fft)
{
nfft >>= 1;
fft_alloc(st.substate, nfft, inverse_fft);
st.tmpbuf.resize(nfft);
st.super_twiddles.resize(nfft);
for (int i=0; i<nfft/2; ++i)
{
double phase = -3.141592653589793238462643383279502884197169399375105820974944 * (double(i+1) / nfft + .5);
if (inverse_fft)
phase *= -1.0;
st.super_twiddles[i] = std::polar(1.0f, float(phase));
}
}
void CKissFFT::fftr(FFTR_STATE &st, const float *timedata, std::complex<float> *freqdata)
{
assert(st.substate.inverse == false);
auto ncfft = st.substate.nfft;
/*perform the parallel fft of two real signals packed in real,imag*/
fft( st.substate, (const std::complex<float>*)timedata, st.tmpbuf.data());
/* The real part of the DC element of the frequency spectrum in st->tmpbuf
* contains the sum of the even-numbered elements of the input time sequence
* The imag part is the sum of the odd-numbered elements
*
* The sum of tdc.r and tdc.i is the sum of the input time sequence.
* yielding DC of input time sequence
* The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1...
* yielding Nyquist bin of input time sequence
*/
auto tdc = st.tmpbuf[0];
freqdata[0].real(tdc.real() + tdc.imag());
freqdata[ncfft].real(tdc.real() - tdc.imag());
freqdata[ncfft].imag(0.f);
freqdata[0].imag(0.f);
for (int k=1; k <= ncfft/2; ++k)
{
auto fpk = st.tmpbuf[k];
auto fpnk = std::conj(st.tmpbuf[ncfft-k]);
auto f1k = fpk + fpnk;
auto f2k = fpk - fpnk;
auto tw = f2k * st.super_twiddles[k-1];
freqdata[k] = 0.5f * (f1k + tw);
freqdata[ncfft-k].real(0.5f * (f1k.real() - tw.real()));
freqdata[ncfft-k].imag(0.5f * (tw.imag() - f1k.imag()));
}
}
void CKissFFT::fftri(FFTR_STATE &st, const std::complex<float> *freqdata, float *timedata)
{
assert(st.substate.inverse == true);
auto ncfft = st.substate.nfft;
st.tmpbuf[0].real(freqdata[0].real() + freqdata[ncfft].real());
st.tmpbuf[0].imag(freqdata[0].real() - freqdata[ncfft].real());
for (int k=1; k <= ncfft/2; ++k)
{
auto fk = freqdata[k];
auto fnkc = std::conj(freqdata[ncfft - k]);
auto fek = fk + fnkc;
auto tmp = fk - fnkc;
auto fok = tmp * st.super_twiddles[k-1];
st.tmpbuf[k] = fek + fok;
st.tmpbuf[ncfft - k] = std::conj(fek - fok);
}
fft (st.substate, st.tmpbuf.data(), (std::complex<float> *)timedata);
}

@ -0,0 +1,35 @@
#ifndef KISS_FFT_H
#define KISS_FFT_H
#include <complex>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "defines.h"
/* for real ffts, we need an even size */
#define kiss_fftr_next_fast_size_real(n) (kiss_fft_next_fast_size( ((n)+1) >> 1) << 1 )
class CKissFFT
{
public:
void fft_alloc(FFT_STATE &state, const int nfft, const bool inverse_fft);
void fft(FFT_STATE &cfg, const std::complex<float> *fin, std::complex<float> *fout);
void fft_stride(FFT_STATE &cfg, const std::complex<float> *fin, std::complex<float> *fout, int fin_stride);
int fft_next_fast_size(int n);
void fftr_alloc(FFTR_STATE &state, int nfft, const bool inverse_fft);
void fftr(FFTR_STATE &cfg,const float *timedata,std::complex<float> *freqdata);
void fftri(FFTR_STATE &cfg,const std::complex<float> *freqdata,float *timedata);
private:
void kf_bfly2(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly3(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly4(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly5(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly_generic(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m, int p);
void kf_work(std::complex<float> *Fout, const std::complex<float> *f, const size_t fstride, int in_stride, int *factors, FFT_STATE &st);
void kf_factor(int n, int *facbuf);
};
#endif

@ -0,0 +1,311 @@
/*---------------------------------------------------------------------------*\
FILE........: lpc.c
AUTHOR......: David Rowe
DATE CREATED: 30 Sep 1990 (!)
Linear Prediction functions written in C.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009-2012 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#define LPC_MAX_N 512 /* maximum no. of samples in frame */
#define PI 3.141592654 /* mathematical constant */
#define ALPHA 1.0
#define BETA 0.94
#include <assert.h>
#include <math.h>
#include "defines.h"
#include "lpc.h"
/*---------------------------------------------------------------------------*\
pre_emp()
Pre-emphasise (high pass filter with zero close to 0 Hz) a frame of
speech samples. Helps reduce dynamic range of LPC spectrum, giving
greater weight and hense a better match to low energy formants.
Should be balanced by de-emphasis of the output speech.
\*---------------------------------------------------------------------------*/
void Clpc::pre_emp(
float Sn_pre[], /* output frame of speech samples */
float Sn[], /* input frame of speech samples */
float *mem, /* Sn[-1]single sample memory */
int Nsam /* number of speech samples to use */
)
{
int i;
for(i=0; i<Nsam; i++)
{
Sn_pre[i] = Sn[i] - ALPHA * mem[0];
mem[0] = Sn[i];
}
}
/*---------------------------------------------------------------------------*\
de_emp()
De-emphasis filter (low pass filter with a pole close to 0 Hz).
\*---------------------------------------------------------------------------*/
void Clpc::de_emp(
float Sn_de[], /* output frame of speech samples */
float Sn[], /* input frame of speech samples */
float *mem, /* Sn[-1]single sample memory */
int Nsam /* number of speech samples to use */
)
{
int i;
for(i=0; i<Nsam; i++)
{
Sn_de[i] = Sn[i] + BETA * mem[0];
mem[0] = Sn_de[i];
}
}
/*---------------------------------------------------------------------------*\
hanning_window()
Hanning windows a frame of speech samples.
\*---------------------------------------------------------------------------*/
void Clpc::hanning_window(
float Sn[], /* input frame of speech samples */
float Wn[], /* output frame of windowed samples */
int Nsam /* number of samples */
)
{
int i; /* loop variable */
for(i=0; i<Nsam; i++)
Wn[i] = Sn[i]*(0.5 - 0.5*cosf(2*PI*(float)i/(Nsam-1)));
}
/*---------------------------------------------------------------------------*\
autocorrelate()
Finds the first P autocorrelation values of an array of windowed speech
samples Sn[].
\*---------------------------------------------------------------------------*/
void Clpc::autocorrelate(
float Sn[], /* frame of Nsam windowed speech samples */
float Rn[], /* array of P+1 autocorrelation coefficients */
int Nsam, /* number of windowed samples to use */
int order /* order of LPC analysis */
)
{
int i,j; /* loop variables */
for(j=0; j<order+1; j++)
{
Rn[j] = 0.0;
for(i=0; i<Nsam-j; i++)
Rn[j] += Sn[i]*Sn[i+j];
}
}
/*---------------------------------------------------------------------------*\
levinson_durbin()
Given P+1 autocorrelation coefficients, finds P Linear Prediction Coeff.
(LPCs) where P is the order of the LPC all-pole model. The Levinson-Durbin
algorithm is used, and is described in:
J. Makhoul
"Linear prediction, a tutorial review"
Proceedings of the IEEE
Vol-63, No. 4, April 1975
\*---------------------------------------------------------------------------*/
void Clpc::levinson_durbin(
float R[], /* order+1 autocorrelation coeff */
float lpcs[], /* order+1 LPC's */
int order /* order of the LPC analysis */
)
{
float a[order+1][order+1];
float sum, e, k;
int i,j; /* loop variables */
e = R[0]; /* Equation 38a, Makhoul */
for(i=1; i<=order; i++)
{
sum = 0.0;
for(j=1; j<=i-1; j++)
sum += a[i-1][j]*R[i-j];
k = -1.0*(R[i] + sum)/e; /* Equation 38b, Makhoul */
if (fabsf(k) > 1.0)
k = 0.0;
a[i][i] = k;
for(j=1; j<=i-1; j++)
a[i][j] = a[i-1][j] + k*a[i-1][i-j]; /* Equation 38c, Makhoul */
e *= (1-k*k); /* Equation 38d, Makhoul */
}
for(i=1; i<=order; i++)
lpcs[i] = a[order][i];
lpcs[0] = 1.0;
}
/*---------------------------------------------------------------------------*\
inverse_filter()
Inverse Filter, A(z). Produces an array of residual samples from an array
of input samples and linear prediction coefficients.
The filter memory is stored in the first order samples of the input array.
\*---------------------------------------------------------------------------*/
void Clpc::inverse_filter(
float Sn[], /* Nsam input samples */
float a[], /* LPCs for this frame of samples */
int Nsam, /* number of samples */
float res[], /* Nsam residual samples */
int order /* order of LPC */
)
{
int i,j; /* loop variables */
for(i=0; i<Nsam; i++)
{
res[i] = 0.0;
for(j=0; j<=order; j++)
res[i] += Sn[i-j]*a[j];
}
}
/*---------------------------------------------------------------------------*\
synthesis_filter()
C version of the Speech Synthesis Filter, 1/A(z). Given an array of
residual or excitation samples, and the the LP filter coefficients, this
function will produce an array of speech samples. This filter structure is
IIR.
The synthesis filter has memory as well, this is treated in the same way
as the memory for the inverse filter (see inverse_filter() notes above).
The difference is that the memory for the synthesis filter is stored in
the output array, wheras the memory of the inverse filter is stored in the
input array.
Note: the calling function must update the filter memory.
\*---------------------------------------------------------------------------*/
void Clpc::synthesis_filter(
float res[], /* Nsam input residual (excitation) samples */
float a[], /* LPCs for this frame of speech samples */
int Nsam, /* number of speech samples */
int order, /* LPC order */
float Sn_[] /* Nsam output synthesised speech samples */
)
{
int i,j; /* loop variables */
/* Filter Nsam samples */
for(i=0; i<Nsam; i++)
{
Sn_[i] = res[i]*a[0];
for(j=1; j<=order; j++)
Sn_[i] -= Sn_[i-j]*a[j];
}
}
/*---------------------------------------------------------------------------*\
find_aks()
This function takes a frame of samples, and determines the linear
prediction coefficients for that frame of samples.
\*---------------------------------------------------------------------------*/
void Clpc::find_aks(
float Sn[], /* Nsam samples with order sample memory */
float a[], /* order+1 LPCs with first coeff 1.0 */
int Nsam, /* number of input speech samples */
int order, /* order of the LPC analysis */
float *E /* residual energy */
)
{
float Wn[LPC_MAX_N]; /* windowed frame of Nsam speech samples */
float R[order+1]; /* order+1 autocorrelation values of Sn[] */
int i;
assert(Nsam < LPC_MAX_N);
hanning_window(Sn,Wn,Nsam);
autocorrelate(Wn,R,Nsam,order);
levinson_durbin(R,a,order);
*E = 0.0;
for(i=0; i<=order; i++)
*E += a[i]*R[i];
if (*E < 0.0)
*E = 1E-12;
}
/*---------------------------------------------------------------------------*\
weight()
Weights a vector of LPCs.
\*---------------------------------------------------------------------------*/
void Clpc::weight(
float ak[], /* vector of order+1 LPCs */
float gamma, /* weighting factor */
int order, /* num LPCs (excluding leading 1.0) */
float akw[] /* weighted vector of order+1 LPCs */
)
{
int i;
for(i=1; i<=order; i++)
akw[i] = ak[i]*powf(gamma,(float)i);
}

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------*\
FILE........: lpc.h
AUTHOR......: David Rowe
DATE CREATED: 24/8/09
Linear Prediction functions written in C.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009-2012 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __LPC__
#define __LPC__
#define LPC_MAX_ORDER 20
class Clpc {
public:
void autocorrelate(float Sn[], float Rn[], int Nsam, int order);
void levinson_durbin(float R[], float lpcs[], int order);
private:
void pre_emp(float Sn_pre[], float Sn[], float *mem, int Nsam);
void de_emp(float Sn_se[], float Sn[], float *mem, int Nsam);
void hanning_window(float Sn[], float Wn[], int Nsam);
void inverse_filter(float Sn[], float a[], int Nsam, float res[], int order);
void synthesis_filter(float res[], float a[], int Nsam, int order, float Sn_[]);
void find_aks(float Sn[], float a[], int Nsam, int order, float *E);
void weight(float ak[], float gamma, int order, float akw[]);
};
#endif

@ -0,0 +1,520 @@
/*---------------------------------------------------------------------------*\
FILE........: nlp.c
AUTHOR......: David Rowe
DATE CREATED: 23/3/93
Non Linear Pitch (NLP) estimation functions.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include "defines.h"
#include "nlp.h"
#include "kiss_fft.h"
extern CKissFFT kiss;
/*---------------------------------------------------------------------------*\
GLOBALS
\*---------------------------------------------------------------------------*/
/* 48 tap 600Hz low pass FIR filter coefficients */
static const float nlp_fir[] =
{
-1.0818124e-03,
-1.1008344e-03,
-9.2768838e-04,
-4.2289438e-04,
5.5034190e-04,
2.0029849e-03,
3.7058509e-03,
5.1449415e-03,
5.5924666e-03,
4.3036754e-03,
8.0284511e-04,
-4.8204610e-03,
-1.1705810e-02,
-1.8199275e-02,
-2.2065282e-02,
-2.0920610e-02,
-1.2808831e-02,
3.2204775e-03,
2.6683811e-02,
5.5520624e-02,
8.6305944e-02,
1.1480192e-01,
1.3674206e-01,
1.4867556e-01,
1.4867556e-01,
1.3674206e-01,
1.1480192e-01,
8.6305944e-02,
5.5520624e-02,
2.6683811e-02,
3.2204775e-03,
-1.2808831e-02,
-2.0920610e-02,
-2.2065282e-02,
-1.8199275e-02,
-1.1705810e-02,
-4.8204610e-03,
8.0284511e-04,
4.3036754e-03,
5.5924666e-03,
5.1449415e-03,
3.7058509e-03,
2.0029849e-03,
5.5034190e-04,
-4.2289438e-04,
-9.2768838e-04,
-1.1008344e-03,
-1.0818124e-03
};
static const float fdmdv_os_filter[]= {
-0.0008215855034550382,
-0.0007833023901802921,
0.001075563790768233,
0.001199092367787555,
-0.001765309502928316,
-0.002055372115328064,
0.002986877604154257,
0.003462567920638414,
-0.004856570111126334,
-0.005563143845031497,
0.007533613299748122,
0.008563932468880897,
-0.01126857129039911,
-0.01280782411693687,
0.01651443896361847,
0.01894875110322284,
-0.02421604439474981,
-0.02845107338464062,
0.03672973563400258,
0.04542046150312214,
-0.06189165826716491,
-0.08721876380763803,
0.1496157094199961,
0.4497962274137046,
0.4497962274137046,
0.1496157094199961,
-0.08721876380763803,
-0.0618916582671649,
0.04542046150312216,
0.03672973563400257,
-0.02845107338464062,
-0.02421604439474984,
0.01894875110322284,
0.01651443896361848,
-0.01280782411693687,
-0.0112685712903991,
0.008563932468880899,
0.007533613299748123,
-0.005563143845031501,
-0.004856570111126346,
0.003462567920638419,
0.002986877604154259,
-0.002055372115328063,
-0.001765309502928318,
0.001199092367787557,
0.001075563790768233,
-0.0007833023901802925,
-0.0008215855034550383
};
/*---------------------------------------------------------------------------*\
nlp_create()
Initialisation function for NLP pitch estimator.
\*---------------------------------------------------------------------------*/
void Cnlp::nlp_create(C2CONST *c2const)
{
int i;
int m = c2const->m_pitch;
int Fs = c2const->Fs;
assert((Fs == 8000) || (Fs == 16000));
snlp.Fs = Fs;
snlp.m = m;
/* if running at 16kHz allocate storage for decimating filter memory */
if (Fs == 16000)
{
snlp.Sn16k.resize(FDMDV_OS_TAPS_16K + c2const->n_samp);
for(i=0; i<FDMDV_OS_TAPS_16K; i++)
{
snlp.Sn16k[i] = 0.0;
}
/* most processing occurs at 8 kHz sample rate so halve m */
m /= 2;
}
assert(m <= PMAX_M);
for(i=0; i<m/DEC; i++)
{
snlp.w[i] = 0.5 - 0.5*cosf(2*PI*i/(m/DEC-1));
}
for(i=0; i<PMAX_M; i++)
snlp.sq[i] = 0.0;
snlp.mem_x = 0.0;
snlp.mem_y = 0.0;
for(i=0; i<NLP_NTAP; i++)
snlp.mem_fir[i] = 0.0;
kiss.fft_alloc(snlp.fft_cfg, PE_FFT_SIZE, false);
}
/*---------------------------------------------------------------------------*\
nlp_destroy()
Shut down function for NLP pitch estimator.
\*---------------------------------------------------------------------------*/
void Cnlp::nlp_destroy()
{
snlp.fft_cfg.twiddles.clear();
}
/*---------------------------------------------------------------------------*\
nlp()
Determines the pitch in samples using the Non Linear Pitch (NLP)
algorithm [1]. Returns the fundamental in Hz. Note that the actual
pitch estimate is for the centre of the M sample Sn[] vector, not
the current N sample input vector. This is (I think) a delay of 2.5
frames with N=80 samples. You should align further analysis using
this pitch estimate to be centred on the middle of Sn[].
Two post processors have been tried, the MBE version (as discussed
in [1]), and a post processor that checks sub-multiples. Both
suffer occasional gross pitch errors (i.e. neither are perfect). In
the presence of background noise the sub-multiple algorithm tends
towards low F0 which leads to better sounding background noise than
the MBE post processor.
A good way to test and develop the NLP pitch estimator is using the
tnlp (codec2/unittest) and the codec2/octave/plnlp.m Octave script.
A pitch tracker searching a few frames forward and backward in time
would be a useful addition.
References:
[1] http://rowetel.com/downloads/1997_rowe_phd_thesis.pdf Chapter 4
\*---------------------------------------------------------------------------*/
float Cnlp::nlp(
float Sn[], /* input speech vector */
int n, /* frames shift (no. new samples in Sn[]) */
float *pitch, /* estimated pitch period in samples at current Fs */
// std::complex<float> Sw[], /* Freq domain version of Sn[] */
// float W[], /* Freq domain window */
float *prev_f0 /* previous pitch f0 in Hz, memory for pitch tracking */
)
{
float notch; /* current notch filter output */
std::complex<float> Fw[PE_FFT_SIZE]; /* DFT of squared signal (input/output) */
float gmax;
int gmax_bin;
int m, i, j;
float best_f0;
m = snlp.m;
/* Square, notch filter at DC, and LP filter vector */
/* If running at 16 kHz decimate to 8 kHz, as NLP ws designed for
Fs = 8kHz. The decimating filter introduces about 3ms of delay,
that shouldn't be a problem as pitch changes slowly. */
if (snlp.Fs == 8000)
{
/* Square latest input samples */
for(i=m-n; i<m; i++)
{
snlp.sq[i] = Sn[i]*Sn[i];
}
}
else
{
assert(snlp.Fs == 16000);
/* re-sample at 8 KHz */
for(i=0; i<n; i++)
{
snlp.Sn16k[FDMDV_OS_TAPS_16K+i] = Sn[m-n+i];
}
m /= 2;
n /= 2;
float Sn8k[n];
fdmdv_16_to_8(Sn8k, &snlp.Sn16k[FDMDV_OS_TAPS_16K], n);
/* Square latest input samples */
for(i=m-n, j=0; i<m; i++, j++)
{
snlp.sq[i] = Sn8k[j]*Sn8k[j];
}
assert(j <= n);
}
for(i=m-n; i<m; i++) /* notch filter at DC */
{
notch = snlp.sq[i] - snlp.mem_x;
notch += COEFF*snlp.mem_y;
snlp.mem_x = snlp.sq[i];
snlp.mem_y = notch;
snlp.sq[i] = notch + 1.0; /* With 0 input vectors to codec,
kiss_fft() would take a long
time to execute when running in
real time. Problem was traced
to kiss_fft function call in
this function. Adding this small
constant fixed problem. Not
exactly sure why. */
}
for(i=m-n; i<m; i++) /* FIR filter vector */
{
for(j=0; j<NLP_NTAP-1; j++)
snlp.mem_fir[j] = snlp.mem_fir[j+1];
snlp.mem_fir[NLP_NTAP-1] = snlp.sq[i];
snlp.sq[i] = 0.0;
for(j=0; j<NLP_NTAP; j++)
snlp.sq[i] += snlp.mem_fir[j]*nlp_fir[j];
}
/* Decimate and DFT */
for(i=0; i<PE_FFT_SIZE; i++)
{
Fw[i].real(0);
Fw[i].imag(0);
}
for(i=0; i<m/DEC; i++)
{
Fw[i].real(snlp.sq[i*DEC]*snlp.w[i]);
}
// FIXME: check if this can be converted to a real fft
// since all imag inputs are 0
codec2_fft_inplace(snlp.fft_cfg, Fw);
for(i=0; i<PE_FFT_SIZE; i++)
Fw[i].real(Fw[i].real() * Fw[i].real() + Fw[i].imag() * Fw[i].imag());
/* todo: express everything in f0, as pitch in samples is dep on Fs */
int pmin = floor(SAMPLE_RATE*P_MIN_S);
int pmax = floor(SAMPLE_RATE*P_MAX_S);
/* find global peak */
gmax = 0.0;
gmax_bin = PE_FFT_SIZE*DEC/pmax;
for(i=PE_FFT_SIZE*DEC/pmax; i<=PE_FFT_SIZE*DEC/pmin; i++)
{
if (Fw[i].real() > gmax)
{
gmax = Fw[i].real();
gmax_bin = i;
}
}
best_f0 = post_process_sub_multiples(Fw, pmax, gmax, gmax_bin, prev_f0);
/* Shift samples in buffer to make room for new samples */
for(i=0; i<m-n; i++)
snlp.sq[i] = snlp.sq[i+n];
/* return pitch period in samples and F0 estimate */
*pitch = (float)snlp.Fs/best_f0;
*prev_f0 = best_f0;
return(best_f0);
}
/*---------------------------------------------------------------------------*\
post_process_sub_multiples()
Given the global maximma of Fw[] we search integer submultiples for
local maxima. If local maxima exist and they are above an
experimentally derived threshold (OK a magic number I pulled out of
the air) we choose the submultiple as the F0 estimate.
The rational for this is that the lowest frequency peak of Fw[]
should be F0, as Fw[] can be considered the autocorrelation function
of Sw[] (the speech spectrum). However sometimes due to phase
effects the lowest frequency maxima may not be the global maxima.
This works OK in practice and favours low F0 values in the presence
of background noise which means the sinusoidal codec does an OK job
of synthesising the background noise. High F0 in background noise
tends to sound more periodic introducing annoying artifacts.
\*---------------------------------------------------------------------------*/
float Cnlp::post_process_sub_multiples(std::complex<float> Fw[], int pmax, float gmax, int gmax_bin, float *prev_f0)
{
int min_bin, cmax_bin;
int mult;
float thresh, best_f0;
int b, bmin, bmax, lmax_bin;
float lmax;
int prev_f0_bin;
/* post process estimate by searching submultiples */
mult = 2;
min_bin = PE_FFT_SIZE*DEC/pmax;
cmax_bin = gmax_bin;
prev_f0_bin = *prev_f0*(PE_FFT_SIZE*DEC)/SAMPLE_RATE;
while(gmax_bin/mult >= min_bin)
{
b = gmax_bin/mult; /* determine search interval */
bmin = 0.8*b;
bmax = 1.2*b;
if (bmin < min_bin)
bmin = min_bin;
/* lower threshold to favour previous frames pitch estimate,
this is a form of pitch tracking */
if ((prev_f0_bin > bmin) && (prev_f0_bin < bmax))
thresh = CNLP*0.5*gmax;
else
thresh = CNLP*gmax;
lmax = 0;
lmax_bin = bmin;
for (b=bmin; b<=bmax; b++) /* look for maximum in interval */
if (Fw[b].real() > lmax)
{
lmax = Fw[b].real();
lmax_bin = b;
}
if (lmax > thresh)
if ((lmax > Fw[lmax_bin-1].real()) && (lmax > Fw[lmax_bin+1].real()))
{
cmax_bin = lmax_bin;
}
mult++;
}
best_f0 = (float)cmax_bin*SAMPLE_RATE/(PE_FFT_SIZE*DEC);
return best_f0;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: fdmdv_16_to_8()
AUTHOR......: David Rowe
DATE CREATED: 9 May 2012
Changes the sample rate of a signal from 16 to 8 kHz.
n is the number of samples at the 8 kHz rate, there are FDMDV_OS*n
samples at the 48 kHz rate. As above however a memory of
FDMDV_OS_TAPS samples is reqd for in16k[] (see t16_8.c unit test as example).
Low pass filter the 16 kHz signal at 4 kHz using the same filter as
the upsampler, then just output every FDMDV_OS-th filtered sample.
Note: this function copied from fdmdv.c, included in nlp.c as a convenience
to avoid linking with another source file.
\*---------------------------------------------------------------------------*/
void Cnlp::fdmdv_16_to_8(float out8k[], float in16k[], int n)
{
float acc;
int i,j,k;
for(i=0, k=0; k<n; i+=FDMDV_OS, k++)
{
acc = 0.0;
for(j=0; j<FDMDV_OS_TAPS_16K; j++)
acc += fdmdv_os_filter[j]*in16k[i-j];
out8k[k] = acc;
}
/* update filter memory */
for(i=-FDMDV_OS_TAPS_16K; i<0; i++)
in16k[i] = in16k[i + n*FDMDV_OS];
}
// there is a little overhead for inplace kiss_fft but this is
// on the powerful platforms like the Raspberry or even x86 PC based ones
// not noticeable
// the reduced usage of RAM and increased performance on STM32 platforms
// should be worth it.
void Cnlp::codec2_fft_inplace(FFT_STATE &cfg, std::complex<float> *inout)
{
std::complex<float> in[512];
// decide whether to use the local stack based buffer for in
// or to allow kiss_fft to allocate RAM
// second part is just to play safe since first method
// is much faster and uses less RAM
if (cfg.nfft <= 512)
{
memcpy(in, inout, cfg.nfft*sizeof(std::complex<float>));
kiss.fft(cfg, in, inout);
}
else
{
kiss.fft(cfg, inout, inout);
}
}

@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------*\
FILE........: nlp.c
AUTHOR......: David Rowe
DATE CREATED: 23/3/93
Non Linear Pitch (NLP) estimation functions.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __NLP__
#define __NLP__
#include <complex>
#include <vector>
#include "defines.h"
/*---------------------------------------------------------------------------*\
DEFINES
\*---------------------------------------------------------------------------*/
#define PMAX_M 320 /* maximum NLP analysis window size */
#define COEFF 0.95 /* notch filter parameter */
#define PE_FFT_SIZE 512 /* DFT size for pitch estimation */
#define DEC 5 /* decimation factor */
#define SAMPLE_RATE 8000
#define PI 3.141592654 /* mathematical constant */
//#define T 0.1 /* threshold for local minima candidate */
#define F0_MAX 500
#define CNLP 0.3 /* post processor constant */
#define NLP_NTAP 48 /* Decimation LPF order */
/* 8 to 16 kHz sample rate conversion */
#define FDMDV_OS 2 /* oversampling rate */
#define FDMDV_OS_TAPS_16K 48 /* number of OS filter taps at 16kHz */
#define FDMDV_OS_TAPS_8K (FDMDV_OS_TAPS_16K/FDMDV_OS) /* number of OS filter taps at 8kHz */
using NLP = struct nlp_tag
{
int Fs; /* sample rate in Hz */
int m;
float w[PMAX_M/DEC]; /* DFT window */
float sq[PMAX_M]; /* squared speech samples */
float mem_x,mem_y; /* memory for notch filter */
float mem_fir[NLP_NTAP]; /* decimation FIR filter memory */
FFT_STATE fft_cfg; /* kiss FFT config */
std::vector<float> Sn16k; /* Fs=16kHz input speech vector */
};
class Cnlp {
public:
void nlp_create(C2CONST *c2const);
void nlp_destroy();
float nlp(float Sn[], int n, float *pitch_samples, float *prev_f0);
void codec2_fft_inplace(FFT_STATE &cfg, std::complex<float> *inout);
private:
float post_process_sub_multiples(std::complex<float> Fw[], int pmax, float gmax, int gmax_bin, float *prev_f0);
void fdmdv_16_to_8(float out8k[], float in16k[], int n);
NLP snlp;
};
#endif

@ -0,0 +1,139 @@
/*
Copyright (C) 2010 Perens LLC <bruce@perens.com>
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "defines.h"
#include "quantise.h"
#include <stdio.h>
/* Compile-time constants */
/* Size of unsigned char in bits. Assumes 8 bits-per-char. */
static const unsigned int WordSize = 8;
/* Mask to pick the bit component out of bitIndex. */
static const unsigned int IndexMask = 0x7;
/* Used to pick the word component out of bitIndex. */
static const unsigned int ShiftRight = 3;
/** Pack a bit field into a bit string, encoding the field in Gray code.
*
* The output is an array of unsigned char data. The fields are efficiently
* packed into the bit string. The Gray coding is a naive attempt to reduce
* the effect of single-bit errors, we expect to do a better job as the
* codec develops.
*
* This code would be simpler if it just set one bit at a time in the string,
* but would hit the same cache line more often. I'm not sure the complexity
* gains us anything here.
*
* Although field is currently of int type rather than unsigned for
* compatibility with the rest of the code, indices are always expected to
* be >= 0.
*/
void CQuantize::pack(
unsigned char *bitArray, /* The output bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
int field, /* The bit field to be packed. */
unsigned int fieldWidth /* Width of the field in BITS, not bytes. */
)
{
pack_natural_or_gray(bitArray, bitIndex, field, fieldWidth, 1);
}
void CQuantize::pack_natural_or_gray(
unsigned char *bitArray, /* The output bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
int field, /* The bit field to be packed. */
unsigned int fieldWidth, /* Width of the field in BITS, not bytes. */
unsigned int gray /* non-zero for gray coding */
)
{
if (gray)
{
/* Convert the field to Gray code */
field = (field >> 1) ^ field;
}
do
{
unsigned int bI = *bitIndex;
unsigned int bitsLeft = WordSize - (bI & IndexMask);
unsigned int sliceWidth = bitsLeft < fieldWidth ? bitsLeft : fieldWidth;
unsigned int wordIndex = bI >> ShiftRight;
bitArray[wordIndex] |= ((unsigned char)((field >> (fieldWidth - sliceWidth)) << (bitsLeft - sliceWidth)));
*bitIndex = bI + sliceWidth;
fieldWidth -= sliceWidth;
}
while ( fieldWidth != 0 );
}
/** Unpack a field from a bit string, converting from Gray code to binary.
*
*/
int CQuantize::unpack(
const unsigned char *bitArray, /* The input bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
unsigned int fieldWidth/* Width of the field in BITS, not bytes. */
)
{
return unpack_natural_or_gray(bitArray, bitIndex, fieldWidth, 1);
}
/** Unpack a field from a bit string, to binary, optionally using
* natural or Gray code.
*
*/
int CQuantize::unpack_natural_or_gray(
const unsigned char *bitArray, /* The input bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
unsigned int fieldWidth,/* Width of the field in BITS, not bytes. */
unsigned int gray /* non-zero for Gray coding */
)
{
unsigned int field = 0;
unsigned int t;
do
{
unsigned int bI = *bitIndex;
unsigned int bitsLeft = WordSize - (bI & IndexMask);
unsigned int sliceWidth = bitsLeft < fieldWidth ? bitsLeft : fieldWidth;
field |= (((bitArray[bI >> ShiftRight] >> (bitsLeft - sliceWidth)) & ((1 << sliceWidth) - 1)) << (fieldWidth - sliceWidth));
*bitIndex = bI + sliceWidth;
fieldWidth -= sliceWidth;
}
while ( fieldWidth != 0 );
if (gray)
{
/* Convert from Gray code to binary. Works for maximum 8-bit fields. */
t = field ^ (field >> 8);
t ^= (t >> 4);
t ^= (t >> 2);
t ^= (t >> 1);
}
else
{
t = field;
}
return t;
}

@ -0,0 +1,247 @@
#include <assert.h>
#include <math.h>
#include "qbase.h"
/*---------------------------------------------------------------------------*\
quantise
Quantises vec by choosing the nearest vector in codebook cb, and
returns the vector index. The squared error of the quantised vector
is added to se.
\*---------------------------------------------------------------------------*/
long CQbase::quantise(const float *cb, float vec[], float w[], int k, int m, float *se)
/* float cb[][K]; current VQ codebook */
/* float vec[]; vector to quantise */
/* float w[]; weighting vector */
/* int k; dimension of vectors */
/* int m; size of codebook */
/* float *se; accumulated squared error */
{
float e; /* current error */
long besti; /* best index so far */
float beste; /* best error so far */
long j;
int i;
float diff;
besti = 0;
beste = 1E32;
for(j=0; j<m; j++)
{
e = 0.0;
for(i=0; i<k; i++)
{
diff = cb[j*k+i]-vec[i];
e += (diff*w[i] * diff*w[i]);
}
if (e < beste)
{
beste = e;
besti = j;
}
}
*se += beste;
return(besti);
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_WoE()
AUTHOR......: Jean-Marc Valin & David Rowe
DATE CREATED: 11 May 2012
Joint Wo and LPC energy vector quantiser developed my Jean-Marc
Valin. Returns index, and updated states xq[].
\*---------------------------------------------------------------------------*/
int CQbase::encode_WoE(MODEL *model, float e, float xq[])
{
int i, n1;
float x[2];
float err[2];
float w[2];
const float *codebook1 = ge_cb[0].cb;
int nb_entries = ge_cb[0].m;
int ndim = ge_cb[0].k;
assert((1<<WO_E_BITS) == nb_entries);
if (e < 0.0) e = 0; /* occasional small negative energies due LPC round off I guess */
x[0] = log10f((model->Wo/PI)*4000.0/50.0)/log10f(2);
x[1] = 10.0*log10f(1e-4 + e);
compute_weights2(x, xq, w);
for (i=0; i<ndim; i++)
err[i] = x[i]-ge_coeff[i]*xq[i];
n1 = find_nearest_weighted(codebook1, nb_entries, err, w, ndim);
for (i=0; i<ndim; i++)
{
xq[i] = ge_coeff[i]*xq[i] + codebook1[ndim*n1+i];
err[i] -= codebook1[ndim*n1+i];
}
//printf("enc: %f %f (%f)(%f) \n", xq[0], xq[1], e, 10.0*log10(1e-4 + e));
return n1;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_WoE()
AUTHOR......: Jean-Marc Valin & David Rowe
DATE CREATED: 11 May 2012
Joint Wo and LPC energy vector quantiser developed my Jean-Marc
Valin. Given index and states xq[], returns Wo & E, and updates
states xq[].
\*---------------------------------------------------------------------------*/
void CQbase::decode_WoE(C2CONST *c2const, MODEL *model, float *e, float xq[], int n1)
{
int i;
const float *codebook1 = ge_cb[0].cb;
int ndim = ge_cb[0].k;
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
for (i=0; i<ndim; i++)
{
xq[i] = ge_coeff[i]*xq[i] + codebook1[ndim*n1+i];
}
//printf("dec: %f %f\n", xq[0], xq[1]);
model->Wo = powf(2.0, xq[0])*(PI*50.0)/4000.0;
/* bit errors can make us go out of range leading to all sorts of
probs like seg faults */
if (model->Wo > Wo_max) model->Wo = Wo_max;
if (model->Wo < Wo_min) model->Wo = Wo_min;
model->L = PI/model->Wo; /* if we quantise Wo re-compute L */
*e = exp10f(xq[1]/10.0);
}
void CQbase::compute_weights2(const float *x, const float *xp, float *w)
{
w[0] = 30;
w[1] = 1;
if (x[1]<0)
{
w[0] *= .6;
w[1] *= .3;
}
if (x[1]<-10)
{
w[0] *= .3;
w[1] *= .3;
}
/* Higher weight if pitch is stable */
if (fabsf(x[0]-xp[0])<.2)
{
w[0] *= 2;
w[1] *= 1.5;
}
else if (fabsf(x[0]-xp[0])>.5) /* Lower if not stable */
{
w[0] *= .5;
}
/* Lower weight for low energy */
if (x[1] < xp[1]-10)
{
w[1] *= .5;
}
if (x[1] < xp[1]-20)
{
w[1] *= .5;
}
//w[0] = 30;
//w[1] = 1;
/* Square the weights because it's applied on the squared error */
w[0] *= w[0];
w[1] *= w[1];
}
int CQbase::find_nearest_weighted(const float *codebook, int nb_entries, float *x, const float *w, int ndim)
{
int i, j;
float min_dist = 1e15;
int nearest = 0;
for (i=0; i<nb_entries; i++)
{
float dist=0;
for (j=0; j<ndim; j++)
dist += w[j]*(x[j]-codebook[i*ndim+j])*(x[j]-codebook[i*ndim+j]);
if (dist<min_dist)
{
min_dist = dist;
nearest = i;
}
}
return nearest;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_log_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Encodes Wo in the log domain using a WO_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
int CQbase::encode_log_Wo(C2CONST *c2const, float Wo, int bits)
{
int index, Wo_levels = 1<<bits;
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float norm;
norm = (log10f(Wo) - log10f(Wo_min))/(log10f(Wo_max) - log10f(Wo_min));
index = floorf(Wo_levels * norm + 0.5);
if (index < 0 ) index = 0;
if (index > (Wo_levels-1)) index = Wo_levels-1;
return index;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_log_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Decodes Wo using a WO_LEVELS quantiser in the log domain.
\*---------------------------------------------------------------------------*/
float CQbase::decode_log_Wo(C2CONST *c2const, int index, int bits)
{
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float step;
float Wo;
int Wo_levels = 1<<bits;
step = (log10f(Wo_max) - log10f(Wo_min))/Wo_levels;
Wo = log10f(Wo_min) + step*(index);
return exp10f(Wo);
}

@ -0,0 +1,39 @@
#ifndef QBASE_H
#define QBASE_H
#include "defines.h"
#define WO_BITS 7
#define WO_LEVELS (1<<WO_BITS)
#define WO_DT_BITS 3
#define E_BITS 5
#define E_LEVELS (1<<E_BITS)
#define E_MIN_DB -10.0
#define E_MAX_DB 40.0
#define LSP_SCALAR_INDEXES 10
#define LSPD_SCALAR_INDEXES 10
#define LSP_PRED_VQ_INDEXES 3
#define WO_E_BITS 8
#define LPCPF_GAMMA 0.5
#define LPCPF_BETA 0.2
class CQbase {
public:
int encode_WoE(MODEL *model, float e, float xq[]);
void decode_WoE(C2CONST *c2const, MODEL *model, float *e, float xq[], int n1);
int encode_log_Wo(C2CONST *c2const, float Wo, int bits);
float decode_log_Wo(C2CONST *c2const, int index, int bits);
protected:
long quantise(const float * cb, float vec[], float w[], int k, int m, float *se);
void compute_weights2(const float *x, const float *xp, float *w);
int find_nearest_weighted(const float *codebook, int nb_entries, float *x, const float *w, int ndim);
const float ge_coeff[2] = { 0.8, 0.9 };
};
#endif

@ -0,0 +1,897 @@
/*---------------------------------------------------------------------------*\
FILE........: quantise.c
AUTHOR......: David Rowe
DATE CREATED: 31/5/92
Quantisation functions for the sinusoidal coder.
\*---------------------------------------------------------------------------*/
/*
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "defines.h"
#include "quantise.h"
#include "lpc.h"
#include "kiss_fft.h"
extern CKissFFT kiss;
#define LSP_DELTA1 0.01 /* grid spacing for LSP root searches */
/*---------------------------------------------------------------------------*\
FUNCTIONS
\*---------------------------------------------------------------------------*/
int CQuantize::lsp_bits(int i)
{
return lsp_cb[i].log2m;
}
int CQuantize::lspd_bits(int i)
{
return lsp_cbd[i].log2m;
}
/*---------------------------------------------------------------------------*\
encode_lspds_scalar()
Scalar/VQ LSP difference quantiser.
\*---------------------------------------------------------------------------*/
void CQuantize::encode_lspds_scalar(int indexes[], float lsp[], int order)
{
int i,k,m;
float lsp_hz[order];
float lsp__hz[order];
float dlsp[order];
float dlsp_[order];
float wt[order];
const float *cb;
float se;
for(i=0; i<order; i++)
{
wt[i] = 1.0;
}
/* convert from radians to Hz so we can use human readable
frequencies */
for(i=0; i<order; i++)
lsp_hz[i] = (4000.0/PI)*lsp[i];
wt[0] = 1.0;
for(i=0; i<order; i++)
{
/* find difference from previous qunatised lsp */
if (i)
dlsp[i] = lsp_hz[i] - lsp__hz[i-1];
else
dlsp[0] = lsp_hz[0];
k = lsp_cbd[i].k;
m = lsp_cbd[i].m;
cb = lsp_cbd[i].cb;
indexes[i] = quantise(cb, &dlsp[i], wt, k, m, &se);
dlsp_[i] = cb[indexes[i]*k];
if (i)
lsp__hz[i] = lsp__hz[i-1] + dlsp_[i];
else
lsp__hz[0] = dlsp_[0];
}
}
void CQuantize::decode_lspds_scalar( float lsp_[], int indexes[], int order)
{
int i,k;
float lsp__hz[order];
float dlsp_[order];
const float *cb;
for(i=0; i<order; i++)
{
k = lsp_cbd[i].k;
cb = lsp_cbd[i].cb;
dlsp_[i] = cb[indexes[i]*k];
if (i)
lsp__hz[i] = lsp__hz[i-1] + dlsp_[i];
else
lsp__hz[0] = dlsp_[0];
lsp_[i] = (PI/4000.0)*lsp__hz[i];
}
}
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX_ENTRIES 16384
void CQuantize::compute_weights(const float *x, float *w, int ndim)
{
int i;
w[0] = MIN(x[0], x[1]-x[0]);
for (i=1; i<ndim-1; i++)
w[i] = MIN(x[i]-x[i-1], x[i+1]-x[i]);
w[ndim-1] = MIN(x[ndim-1]-x[ndim-2], PI-x[ndim-1]);
for (i=0; i<ndim; i++)
w[i] = 1./(.01+w[i]);
}
int CQuantize::find_nearest(const float *codebook, int nb_entries, float *x, int ndim)
{
int i, j;
float min_dist = 1e15;
int nearest = 0;
for (i=0; i<nb_entries; i++)
{
float dist=0;
for (j=0; j<ndim; j++)
dist += (x[j]-codebook[i*ndim+j])*(x[j]-codebook[i*ndim+j]);
if (dist<min_dist)
{
min_dist = dist;
nearest = i;
}
}
return nearest;
}
int CQuantize::check_lsp_order(float lsp[], int order)
{
int i;
float tmp;
int swaps = 0;
for(i=1; i<order; i++)
if (lsp[i] < lsp[i-1])
{
//fprintf(stderr, "swap %d\n",i);
swaps++;
tmp = lsp[i-1];
lsp[i-1] = lsp[i]-0.1;
lsp[i] = tmp+0.1;
i = 1; /* start check again, as swap may have caused out of order */
}
return swaps;
}
/*---------------------------------------------------------------------------*\
lpc_post_filter()
Applies a post filter to the LPC synthesis filter power spectrum
Pw, which supresses the inter-formant energy.
The algorithm is from p267 (Section 8.6) of "Digital Speech",
edited by A.M. Kondoz, 1994 published by Wiley and Sons. Chapter 8
of this text is on the MBE vocoder, and this is a freq domain
adaptation of post filtering commonly used in CELP.
I used the Octave simulation lpcpf.m to get an understanding of the
algorithm.
Requires two more FFTs which is significantly more MIPs. However
it should be possible to implement this more efficiently in the
time domain. Just not sure how to handle relative time delays
between the synthesis stage and updating these coeffs. A smaller
FFT size might also be accetable to save CPU.
TODO:
[ ] sync var names between Octave and C version
[ ] doc gain normalisation
[ ] I think the first FFT is not rqd as we do the same
thing in aks_to_M2().
\*---------------------------------------------------------------------------*/
void CQuantize::lpc_post_filter(FFTR_STATE *fftr_fwd_cfg, float Pw[], float ak[], int order, float beta, float gamma, int bass_boost, float E)
{
int i;
float x[FFT_ENC]; /* input to FFTs */
std::complex<float> Ww[FFT_ENC/2+1]; /* weighting spectrum */
float Rw[FFT_ENC/2+1]; /* R = WA */
float e_before, e_after, gain;
float Pfw;
float max_Rw, min_Rw;
float coeff;
/* Determine weighting filter spectrum W(exp(jw)) ---------------*/
for(i=0; i<FFT_ENC; i++)
{
x[i] = 0.0;
}
x[0] = ak[0];
coeff = gamma;
for(i=1; i<=order; i++)
{
x[i] = ak[i] * coeff;
coeff *= gamma;
}
kiss.fftr(*fftr_fwd_cfg, x, Ww);
for(i=0; i<FFT_ENC/2; i++)
{
Ww[i].real(Ww[i].real() * Ww[i].real() + Ww[i].imag() * Ww[i].imag());
}
/* Determined combined filter R = WA ---------------------------*/
max_Rw = 0.0;
min_Rw = 1E32;
for(i=0; i<FFT_ENC/2; i++)
{
Rw[i] = sqrtf(Ww[i].real() * Pw[i]);
if (Rw[i] > max_Rw)
max_Rw = Rw[i];
if (Rw[i] < min_Rw)
min_Rw = Rw[i];
}
/* create post filter mag spectrum and apply ------------------*/
/* measure energy before post filtering */
e_before = 1E-4;
for(i=0; i<FFT_ENC/2; i++)
e_before += Pw[i];
/* apply post filter and measure energy */
e_after = 1E-4;
for(i=0; i<FFT_ENC/2; i++)
{
Pfw = powf(Rw[i], beta);
Pw[i] *= Pfw * Pfw;
e_after += Pw[i];
}
gain = e_before/e_after;
/* apply gain factor to normalise energy, and LPC Energy */
gain *= E;
for(i=0; i<FFT_ENC/2; i++)
{
Pw[i] *= gain;
}
if (bass_boost)
{
/* add 3dB to first 1 kHz to account for LP effect of PF */
for(i=0; i<FFT_ENC/8; i++)
{
Pw[i] *= 1.4*1.4;
}
}
}
/*---------------------------------------------------------------------------*\
aks_to_M2()
Transforms the linear prediction coefficients to spectral amplitude
samples. This function determines A(m) from the average energy per
band using an FFT.
\*---------------------------------------------------------------------------*/
void CQuantize::aks_to_M2(
FFTR_STATE * fftr_fwd_cfg,
float ak[], /* LPC's */
int order,
MODEL *model, /* sinusoidal model parameters for this frame */
float E, /* energy term */
float *snr, /* signal to noise ratio for this frame in dB */
int sim_pf, /* true to simulate a post filter */
int pf, /* true to enable actual LPC post filter */
int bass_boost, /* enable LPC filter 0-1kHz 3dB boost */
float beta,
float gamma, /* LPC post filter parameters */
std::complex<float> Aw[] /* output power spectrum */
)
{
int i,m; /* loop variables */
int am,bm; /* limits of current band */
float r; /* no. rads/bin */
float Em; /* energy in band */
float Am; /* spectral amplitude sample */
float signal, noise;
r = TWO_PI/(FFT_ENC);
/* Determine DFT of A(exp(jw)) --------------------------------------------*/
{
float a[FFT_ENC]; /* input to FFT for power spectrum */
for(i=0; i<FFT_ENC; i++)
{
a[i] = 0.0;
}
for(i=0; i<=order; i++)
a[i] = ak[i];
kiss.fftr(*fftr_fwd_cfg, a, Aw);
}
/* Determine power spectrum P(w) = E/(A(exp(jw))^2 ------------------------*/
float Pw[FFT_ENC/2];
for(i=0; i<FFT_ENC/2; i++)
{
Pw[i] = 1.0/(Aw[i].real() * Aw[i].real() + Aw[i].imag() * Aw[i].imag() + 1E-6);
}
if (pf)
lpc_post_filter(fftr_fwd_cfg, Pw, ak, order, beta, gamma, bass_boost, E);
else
{
for(i=0; i<FFT_ENC/2; i++)
{
Pw[i] *= E;
}
}
/* Determine magnitudes from P(w) ----------------------------------------*/
/* when used just by decoder {A} might be all zeroes so init signal
and noise to prevent log(0) errors */
signal = 1E-30;
noise = 1E-32;
for(m=1; m<=model->L; m++)
{
am = (int)((m - 0.5)*model->Wo/r + 0.5);
bm = (int)((m + 0.5)*model->Wo/r + 0.5);
// FIXME: With arm_rfft_fast_f32 we have to use this
// otherwise sometimes a to high bm is calculated
// which causes trouble later in the calculation
// chain
// it seems for some reason model->Wo is calculated somewhat too high
if (bm>FFT_ENC/2)
{
bm = FFT_ENC/2;
}
Em = 0.0;
for(i=am; i<bm; i++)
Em += Pw[i];
Am = sqrtf(Em);
signal += model->A[m]*model->A[m];
noise += (model->A[m] - Am)*(model->A[m] - Am);
/* This code significantly improves perf of LPC model, in
particular when combined with phase0. The LPC spectrum tends
to track just under the peaks of the spectral envelope, and
just above nulls. This algorithm does the reverse to
compensate - raising the amplitudes of spectral peaks, while
attenuating the null. This enhances the formants, and
supresses the energy between formants. */
if (sim_pf)
{
if (Am > model->A[m])
Am *= 0.7;
if (Am < model->A[m])
Am *= 1.4;
}
model->A[m] = Am;
}
*snr = 10.0*log10f(signal/noise);
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Encodes Wo using a WO_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
int CQuantize::encode_Wo(C2CONST *c2const, float Wo, int bits)
{
int index, Wo_levels = 1<<bits;
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float norm;
norm = (Wo - Wo_min)/(Wo_max - Wo_min);
index = floorf(Wo_levels * norm + 0.5);
if (index < 0 ) index = 0;
if (index > (Wo_levels-1)) index = Wo_levels-1;
return index;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Decodes Wo using a WO_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
float CQuantize::decode_Wo(C2CONST *c2const, int index, int bits)
{
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float step;
float Wo;
int Wo_levels = 1<<bits;
step = (Wo_max - Wo_min)/Wo_levels;
Wo = Wo_min + step*(index);
return Wo;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: speech_to_uq_lsps()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Analyse a windowed frame of time domain speech to determine LPCs
which are the converted to LSPs for quantisation and transmission
over the channel.
\*---------------------------------------------------------------------------*/
float CQuantize::speech_to_uq_lsps(float lsp[], float ak[], float Sn[], float w[], int m_pitch, int order)
{
int i, roots;
float Wn[m_pitch];
float R[order+1];
float e, E;
Clpc lpc;
e = 0.0;
for(i=0; i<m_pitch; i++)
{
Wn[i] = Sn[i]*w[i];
e += Wn[i]*Wn[i];
}
/* trap 0 energy case as LPC analysis will fail */
if (e == 0.0)
{
for(i=0; i<order; i++)
lsp[i] = (PI/order)*(float)i;
return 0.0;
}
lpc.autocorrelate(Wn, R, m_pitch, order);
lpc.levinson_durbin(R, ak, order);
E = 0.0;
for(i=0; i<=order; i++)
E += ak[i]*R[i];
/* 15 Hz BW expansion as I can't hear the difference and it may help
help occasional fails in the LSP root finding. Important to do this
after energy calculation to avoid -ve energy values.
*/
for(i=0; i<=order; i++)
ak[i] *= powf(0.994,(float)i);
roots = lpc_to_lsp(ak, order, lsp, 5, LSP_DELTA1);
if (roots != order)
{
/* if root finding fails use some benign LSP values instead */
for(i=0; i<order; i++)
lsp[i] = (PI/order)*(float)i;
}
return E;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_lsps_scalar()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Thirty-six bit sclar LSP quantiser. From a vector of unquantised
(floating point) LSPs finds the quantised LSP indexes.
\*---------------------------------------------------------------------------*/
void CQuantize::encode_lsps_scalar(int indexes[], float lsp[], int order)
{
int i,k,m;
float wt[1];
float lsp_hz[order];
const float *cb;
float se;
/* convert from radians to Hz so we can use human readable
frequencies */
for(i=0; i<order; i++)
lsp_hz[i] = (4000.0/PI)*lsp[i];
/* scalar quantisers */
wt[0] = 1.0;
for(i=0; i<order; i++)
{
k = lsp_cb[i].k;
m = lsp_cb[i].m;
cb = lsp_cb[i].cb;
indexes[i] = quantise(cb, &lsp_hz[i], wt, k, m, &se);
}
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_lsps_scalar()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
From a vector of quantised LSP indexes, returns the quantised
(floating point) LSPs.
\*---------------------------------------------------------------------------*/
void CQuantize::decode_lsps_scalar(float lsp[], int indexes[], int order)
{
int i,k;
float lsp_hz[order];
const float *cb;
for(i=0; i<order; i++)
{
k = lsp_cb[i].k;
cb = lsp_cb[i].cb;
lsp_hz[i] = cb[indexes[i]*k];
}
/* convert back to radians */
for(i=0; i<order; i++)
lsp[i] = (PI/4000.0)*lsp_hz[i];
}
/*---------------------------------------------------------------------------*\
FUNCTION....: bw_expand_lsps()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Applies Bandwidth Expansion (BW) to a vector of LSPs. Prevents any
two LSPs getting too close together after quantisation. We know
from experiment that LSP quantisation errors < 12.5Hz (25Hz step
size) are inaudible so we use that as the minimum LSP separation.
\*---------------------------------------------------------------------------*/
void CQuantize::bw_expand_lsps(float lsp[], int order, float min_sep_low, float min_sep_high)
{
int i;
for(i=1; i<4; i++)
{
if ((lsp[i] - lsp[i-1]) < min_sep_low*(PI/4000.0))
lsp[i] = lsp[i-1] + min_sep_low*(PI/4000.0);
}
/* As quantiser gaps increased, larger BW expansion was required
to prevent twinkly noises. This may need more experiment for
different quanstisers.
*/
for(i=4; i<order; i++)
{
if (lsp[i] - lsp[i-1] < min_sep_high*(PI/4000.0))
lsp[i] = lsp[i-1] + min_sep_high*(PI/4000.0);
}
}
/*---------------------------------------------------------------------------*\
FUNCTION....: apply_lpc_correction()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Apply first harmonic LPC correction at decoder. This helps improve
low pitch males after LPC modelling, like hts1a and morig.
\*---------------------------------------------------------------------------*/
void CQuantize::apply_lpc_correction(MODEL *model)
{
if (model->Wo < (PI*150.0/4000))
{
model->A[1] *= 0.032;
}
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_energy()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Encodes LPC energy using an E_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
int CQuantize::encode_energy(float e, int bits)
{
int index, e_levels = 1<<bits;
float e_min = E_MIN_DB;
float e_max = E_MAX_DB;
float norm;
e = 10.0*log10f(e);
norm = (e - e_min)/(e_max - e_min);
index = floorf(e_levels * norm + 0.5);
if (index < 0 ) index = 0;
if (index > (e_levels-1)) index = e_levels-1;
return index;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_energy()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Decodes energy using a E_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
float CQuantize::decode_energy(int index, int bits)
{
float e_min = E_MIN_DB;
float e_max = E_MAX_DB;
float step;
float e;
int e_levels = 1<<bits;
step = (e_max - e_min)/e_levels;
e = e_min + step*(index);
e = exp10f(e/10.0);
return e;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: lpc_to_lsp()
AUTHOR......: David Rowe
DATE CREATED: 24/2/93
This function converts LPC coefficients to LSP coefficients.
\*---------------------------------------------------------------------------*/
int CQuantize::lpc_to_lsp(float *a, int order, float *freq, int nb, float delta)
/* float *a lpc coefficients */
/* int order order of LPC coefficients (10) */
/* float *freq LSP frequencies in radians */
/* int nb number of sub-intervals (4) */
/* float delta grid spacing interval (0.02) */
{
float psuml,psumr,psumm,temp_xr,xl,xr,xm = 0;
float temp_psumr;
int i,j,m,flag,k;
float *px; /* ptrs of respective P'(z) & Q'(z) */
float *qx;
float *p;
float *q;
float *pt; /* ptr used for cheb_poly_eval()
whether P' or Q' */
int roots=0; /* number of roots found */
float Q[order + 1];
float P[order + 1];
flag = 1;
m = order/2; /* order of P'(z) & Q'(z) polynimials */
/* Allocate memory space for polynomials */
/* determine P'(z)'s and Q'(z)'s coefficients where
P'(z) = P(z)/(1 + z^(-1)) and Q'(z) = Q(z)/(1-z^(-1)) */
px = P; /* initilaise ptrs */
qx = Q;
p = px;
q = qx;
*px++ = 1.0;
*qx++ = 1.0;
for(i=1; i<=m; i++)
{
*px++ = a[i]+a[order+1-i]-*p++;
*qx++ = a[i]-a[order+1-i]+*q++;
}
px = P;
qx = Q;
for(i=0; i<m; i++)
{
*px = 2**px;
*qx = 2**qx;
px++;
qx++;
}
px = P; /* re-initialise ptrs */
qx = Q;
/* Search for a zero in P'(z) polynomial first and then alternate to Q'(z).
Keep alternating between the two polynomials as each zero is found */
xr = 0; /* initialise xr to zero */
xl = 1.0; /* start at point xl = 1 */
for(j=0; j<order; j++)
{
if(j%2) /* determines whether P' or Q' is eval. */
pt = qx;
else
pt = px;
psuml = cheb_poly_eva(pt,xl,order); /* evals poly. at xl */
flag = 1;
while(flag && (xr >= -1.0))
{
xr = xl - delta ; /* interval spacing */
psumr = cheb_poly_eva(pt,xr,order);/* poly(xl-delta_x) */
temp_psumr = psumr;
temp_xr = xr;
/* if no sign change increment xr and re-evaluate
poly(xr). Repeat til sign change. if a sign change has
occurred the interval is bisected and then checked again
for a sign change which determines in which interval the
zero lies in. If there is no sign change between poly(xm)
and poly(xl) set interval between xm and xr else set
interval between xl and xr and repeat till root is located
within the specified limits */
if(((psumr*psuml)<0.0) || (psumr == 0.0))
{
roots++;
psumm=psuml;
for(k=0; k<=nb; k++)
{
xm = (xl+xr)/2; /* bisect the interval */
psumm=cheb_poly_eva(pt,xm,order);
if(psumm*psuml>0.)
{
psuml=psumm;
xl=xm;
}
else
{
psumr=psumm;
xr=xm;
}
}
/* once zero is found, reset initial interval to xr */
freq[j] = (xm);
xl = xm;
flag = 0; /* reset flag for next search */
}
else
{
psuml=temp_psumr;
xl=temp_xr;
}
}
}
/* convert from x domain to radians */
for(i=0; i<order; i++)
{
freq[i] = acosf(freq[i]);
}
return(roots);
}
/*---------------------------------------------------------------------------*\
FUNCTION....: cheb_poly_eva()
AUTHOR......: David Rowe
DATE CREATED: 24/2/93
This function evalutes a series of chebyshev polynomials
FIXME: performing memory allocation at run time is very inefficient,
replace with stack variables of MAX_P size.
\*---------------------------------------------------------------------------*/
float CQuantize::cheb_poly_eva(float *coef,float x,int order)
/* float coef[] coefficients of the polynomial to be evaluated */
/* float x the point where polynomial is to be evaluated */
/* int order order of the polynomial */
{
int i;
float *t,*u,*v,sum;
float T[(order / 2) + 1];
/* Initialise pointers */
t = T; /* T[i-2] */
*t++ = 1.0;
u = t--; /* T[i-1] */
*u++ = x;
v = u--; /* T[i] */
/* Evaluate chebyshev series formulation using iterative approach */
for(i=2; i<=order/2; i++)
*v++ = (2*x)*(*u++) - *t++; /* T[i] = 2*x*T[i-1] - T[i-2] */
sum=0.0; /* initialise sum to zero */
t = T; /* reset pointer */
/* Evaluate polynomial and return value also free memory space */
for(i=0; i<=order/2; i++)
sum+=coef[(order/2)-i]**t++;
return sum;
}

@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------*\
FILE........: quantise.h
AUTHOR......: David Rowe
DATE CREATED: 31/5/92
Quantisation functions for the sinusoidal coder.
\*---------------------------------------------------------------------------*/
/*
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __QUANTISE__
#define __QUANTISE__
#include <complex>
#include "qbase.h"
class CQuantize : public CQbase {
public:
void aks_to_M2(FFTR_STATE *fftr_fwd_cfg, float ak[], int order, MODEL *model, float E, float *snr, int sim_pf, int pf, int bass_boost, float beta, float gamma, std::complex<float> Aw[]);
int encode_Wo(C2CONST *c2const, float Wo, int bits);
float decode_Wo(C2CONST *c2const, int index, int bits);
void encode_lsps_scalar(int indexes[], float lsp[], int order);
void decode_lsps_scalar(float lsp[], int indexes[], int order);
void encode_lspds_scalar(int indexes[], float lsp[], int order);
void decode_lspds_scalar(float lsp[], int indexes[], int order);
int encode_energy(float e, int bits);
float decode_energy(int index, int bits);
void pack(unsigned char * bits, unsigned int *nbit, int index, unsigned int index_bits);
void pack_natural_or_gray(unsigned char * bits, unsigned int *nbit, int index, unsigned int index_bits, unsigned int gray);
int unpack(const unsigned char * bits, unsigned int *nbit, unsigned int index_bits);
int unpack_natural_or_gray(const unsigned char * bits, unsigned int *nbit, unsigned int index_bits, unsigned int gray);
int lsp_bits(int i);
int lspd_bits(int i);
void apply_lpc_correction(MODEL *model);
float speech_to_uq_lsps(float lsp[], float ak[], float Sn[], float w[], int m_pitch, int order);
int check_lsp_order(float lsp[], int lpc_order);
void bw_expand_lsps(float lsp[], int order, float min_sep_low, float min_sep_high);
private:
void compute_weights(const float *x, float *w, int ndim);
int find_nearest(const float *codebook, int nb_entries, float *x, int ndim);
void lpc_post_filter(FFTR_STATE *fftr_fwd_cfg, float Pw[], float ak[], int order, float beta, float gamma, int bass_boost, float E);
int lpc_to_lsp (float *a, int lpcrdr, float *freq, int nb, float delta);
float cheb_poly_eva(float *coef,float x,int order);
};
#endif

@ -0,0 +1,130 @@
/*
* Copyright (C) 2015 by Jonathan Naylor G4KLX
*
* 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 "crs129.h"
#include <cstdio>
#include <cassert>
#include <cstring>
const unsigned int NPAR = 3U;
/* Maximum degree of various polynomials. */
//const unsigned int MAXDEG = NPAR * 2U;
/* Generator Polynomial */
const unsigned char POLY[] = {64U, 56U, 14U, 1U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U};
const unsigned char EXP_TABLE[] = {
0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1DU, 0x3AU, 0x74U, 0xE8U, 0xCDU, 0x87U, 0x13U, 0x26U,
0x4CU, 0x98U, 0x2DU, 0x5AU, 0xB4U, 0x75U, 0xEAU, 0xC9U, 0x8FU, 0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0xC0U,
0x9DU, 0x27U, 0x4EU, 0x9CU, 0x25U, 0x4AU, 0x94U, 0x35U, 0x6AU, 0xD4U, 0xB5U, 0x77U, 0xEEU, 0xC1U, 0x9FU, 0x23U,
0x46U, 0x8CU, 0x05U, 0x0AU, 0x14U, 0x28U, 0x50U, 0xA0U, 0x5DU, 0xBAU, 0x69U, 0xD2U, 0xB9U, 0x6FU, 0xDEU, 0xA1U,
0x5FU, 0xBEU, 0x61U, 0xC2U, 0x99U, 0x2FU, 0x5EU, 0xBCU, 0x65U, 0xCAU, 0x89U, 0x0FU, 0x1EU, 0x3CU, 0x78U, 0xF0U,
0xFDU, 0xE7U, 0xD3U, 0xBBU, 0x6BU, 0xD6U, 0xB1U, 0x7FU, 0xFEU, 0xE1U, 0xDFU, 0xA3U, 0x5BU, 0xB6U, 0x71U, 0xE2U,
0xD9U, 0xAFU, 0x43U, 0x86U, 0x11U, 0x22U, 0x44U, 0x88U, 0x0DU, 0x1AU, 0x34U, 0x68U, 0xD0U, 0xBDU, 0x67U, 0xCEU,
0x81U, 0x1FU, 0x3EU, 0x7CU, 0xF8U, 0xEDU, 0xC7U, 0x93U, 0x3BU, 0x76U, 0xECU, 0xC5U, 0x97U, 0x33U, 0x66U, 0xCCU,
0x85U, 0x17U, 0x2EU, 0x5CU, 0xB8U, 0x6DU, 0xDAU, 0xA9U, 0x4FU, 0x9EU, 0x21U, 0x42U, 0x84U, 0x15U, 0x2AU, 0x54U,
0xA8U, 0x4DU, 0x9AU, 0x29U, 0x52U, 0xA4U, 0x55U, 0xAAU, 0x49U, 0x92U, 0x39U, 0x72U, 0xE4U, 0xD5U, 0xB7U, 0x73U,
0xE6U, 0xD1U, 0xBFU, 0x63U, 0xC6U, 0x91U, 0x3FU, 0x7EU, 0xFCU, 0xE5U, 0xD7U, 0xB3U, 0x7BU, 0xF6U, 0xF1U, 0xFFU,
0xE3U, 0xDBU, 0xABU, 0x4BU, 0x96U, 0x31U, 0x62U, 0xC4U, 0x95U, 0x37U, 0x6EU, 0xDCU, 0xA5U, 0x57U, 0xAEU, 0x41U,
0x82U, 0x19U, 0x32U, 0x64U, 0xC8U, 0x8DU, 0x07U, 0x0EU, 0x1CU, 0x38U, 0x70U, 0xE0U, 0xDDU, 0xA7U, 0x53U, 0xA6U,
0x51U, 0xA2U, 0x59U, 0xB2U, 0x79U, 0xF2U, 0xF9U, 0xEFU, 0xC3U, 0x9BU, 0x2BU, 0x56U, 0xACU, 0x45U, 0x8AU, 0x09U,
0x12U, 0x24U, 0x48U, 0x90U, 0x3DU, 0x7AU, 0xF4U, 0xF5U, 0xF7U, 0xF3U, 0xFBU, 0xEBU, 0xCBU, 0x8BU, 0x0BU, 0x16U,
0x2CU, 0x58U, 0xB0U, 0x7DU, 0xFAU, 0xE9U, 0xCFU, 0x83U, 0x1BU, 0x36U, 0x6CU, 0xD8U, 0xADU, 0x47U, 0x8EU, 0x01U,
0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1DU, 0x3AU, 0x74U, 0xE8U, 0xCDU, 0x87U, 0x13U, 0x26U, 0x4CU,
0x98U, 0x2DU, 0x5AU, 0xB4U, 0x75U, 0xEAU, 0xC9U, 0x8FU, 0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0xC0U, 0x9DU,
0x27U, 0x4EU, 0x9CU, 0x25U, 0x4AU, 0x94U, 0x35U, 0x6AU, 0xD4U, 0xB5U, 0x77U, 0xEEU, 0xC1U, 0x9FU, 0x23U, 0x46U,
0x8CU, 0x05U, 0x0AU, 0x14U, 0x28U, 0x50U, 0xA0U, 0x5DU, 0xBAU, 0x69U, 0xD2U, 0xB9U, 0x6FU, 0xDEU, 0xA1U, 0x5FU,
0xBEU, 0x61U, 0xC2U, 0x99U, 0x2FU, 0x5EU, 0xBCU, 0x65U, 0xCAU, 0x89U, 0x0FU, 0x1EU, 0x3CU, 0x78U, 0xF0U, 0xFDU,
0xE7U, 0xD3U, 0xBBU, 0x6BU, 0xD6U, 0xB1U, 0x7FU, 0xFEU, 0xE1U, 0xDFU, 0xA3U, 0x5BU, 0xB6U, 0x71U, 0xE2U, 0xD9U,
0xAFU, 0x43U, 0x86U, 0x11U, 0x22U, 0x44U, 0x88U, 0x0DU, 0x1AU, 0x34U, 0x68U, 0xD0U, 0xBDU, 0x67U, 0xCEU, 0x81U,
0x1FU, 0x3EU, 0x7CU, 0xF8U, 0xEDU, 0xC7U, 0x93U, 0x3BU, 0x76U, 0xECU, 0xC5U, 0x97U, 0x33U, 0x66U, 0xCCU, 0x85U,
0x17U, 0x2EU, 0x5CU, 0xB8U, 0x6DU, 0xDAU, 0xA9U, 0x4FU, 0x9EU, 0x21U, 0x42U, 0x84U, 0x15U, 0x2AU, 0x54U, 0xA8U,
0x4DU, 0x9AU, 0x29U, 0x52U, 0xA4U, 0x55U, 0xAAU, 0x49U, 0x92U, 0x39U, 0x72U, 0xE4U, 0xD5U, 0xB7U, 0x73U, 0xE6U,
0xD1U, 0xBFU, 0x63U, 0xC6U, 0x91U, 0x3FU, 0x7EU, 0xFCU, 0xE5U, 0xD7U, 0xB3U, 0x7BU, 0xF6U, 0xF1U, 0xFFU, 0xE3U,
0xDBU, 0xABU, 0x4BU, 0x96U, 0x31U, 0x62U, 0xC4U, 0x95U, 0x37U, 0x6EU, 0xDCU, 0xA5U, 0x57U, 0xAEU, 0x41U, 0x82U,
0x19U, 0x32U, 0x64U, 0xC8U, 0x8DU, 0x07U, 0x0EU, 0x1CU, 0x38U, 0x70U, 0xE0U, 0xDDU, 0xA7U, 0x53U, 0xA6U, 0x51U,
0xA2U, 0x59U, 0xB2U, 0x79U, 0xF2U, 0xF9U, 0xEFU, 0xC3U, 0x9BU, 0x2BU, 0x56U, 0xACU, 0x45U, 0x8AU, 0x09U, 0x12U,
0x24U, 0x48U, 0x90U, 0x3DU, 0x7AU, 0xF4U, 0xF5U, 0xF7U, 0xF3U, 0xFBU, 0xEBU, 0xCBU, 0x8BU, 0x0BU, 0x16U, 0x2CU,
0x58U, 0xB0U, 0x7DU, 0xFAU, 0xE9U, 0xCFU, 0x83U, 0x1BU, 0x36U, 0x6CU, 0xD8U, 0xADU, 0x47U, 0x8EU, 0x01U, 0x00U};
const unsigned char LOG_TABLE[] = {
0x00U, 0x00U, 0x01U, 0x19U, 0x02U, 0x32U, 0x1AU, 0xC6U, 0x03U, 0xDFU, 0x33U, 0xEEU, 0x1BU, 0x68U, 0xC7U, 0x4BU,
0x04U, 0x64U, 0xE0U, 0x0EU, 0x34U, 0x8DU, 0xEFU, 0x81U, 0x1CU, 0xC1U, 0x69U, 0xF8U, 0xC8U, 0x08U, 0x4CU, 0x71U,
0x05U, 0x8AU, 0x65U, 0x2FU, 0xE1U, 0x24U, 0x0FU, 0x21U, 0x35U, 0x93U, 0x8EU, 0xDAU, 0xF0U, 0x12U, 0x82U, 0x45U,
0x1DU, 0xB5U, 0xC2U, 0x7DU, 0x6AU, 0x27U, 0xF9U, 0xB9U, 0xC9U, 0x9AU, 0x09U, 0x78U, 0x4DU, 0xE4U, 0x72U, 0xA6U,
0x06U, 0xBFU, 0x8BU, 0x62U, 0x66U, 0xDDU, 0x30U, 0xFDU, 0xE2U, 0x98U, 0x25U, 0xB3U, 0x10U, 0x91U, 0x22U, 0x88U,
0x36U, 0xD0U, 0x94U, 0xCEU, 0x8FU, 0x96U, 0xDBU, 0xBDU, 0xF1U, 0xD2U, 0x13U, 0x5CU, 0x83U, 0x38U, 0x46U, 0x40U,
0x1EU, 0x42U, 0xB6U, 0xA3U, 0xC3U, 0x48U, 0x7EU, 0x6EU, 0x6BU, 0x3AU, 0x28U, 0x54U, 0xFAU, 0x85U, 0xBAU, 0x3DU,
0xCAU, 0x5EU, 0x9BU, 0x9FU, 0x0AU, 0x15U, 0x79U, 0x2BU, 0x4EU, 0xD4U, 0xE5U, 0xACU, 0x73U, 0xF3U, 0xA7U, 0x57U,
0x07U, 0x70U, 0xC0U, 0xF7U, 0x8CU, 0x80U, 0x63U, 0x0DU, 0x67U, 0x4AU, 0xDEU, 0xEDU, 0x31U, 0xC5U, 0xFEU, 0x18U,
0xE3U, 0xA5U, 0x99U, 0x77U, 0x26U, 0xB8U, 0xB4U, 0x7CU, 0x11U, 0x44U, 0x92U, 0xD9U, 0x23U, 0x20U, 0x89U, 0x2EU,
0x37U, 0x3FU, 0xD1U, 0x5BU, 0x95U, 0xBCU, 0xCFU, 0xCDU, 0x90U, 0x87U, 0x97U, 0xB2U, 0xDCU, 0xFCU, 0xBEU, 0x61U,
0xF2U, 0x56U, 0xD3U, 0xABU, 0x14U, 0x2AU, 0x5DU, 0x9EU, 0x84U, 0x3CU, 0x39U, 0x53U, 0x47U, 0x6DU, 0x41U, 0xA2U,
0x1FU, 0x2DU, 0x43U, 0xD8U, 0xB7U, 0x7BU, 0xA4U, 0x76U, 0xC4U, 0x17U, 0x49U, 0xECU, 0x7FU, 0x0CU, 0x6FU, 0xF6U,
0x6CU, 0xA1U, 0x3BU, 0x52U, 0x29U, 0x9DU, 0x55U, 0xAAU, 0xFBU, 0x60U, 0x86U, 0xB1U, 0xBBU, 0xCCU, 0x3EU, 0x5AU,
0xCBU, 0x59U, 0x5FU, 0xB0U, 0x9CU, 0xA9U, 0xA0U, 0x51U, 0x0BU, 0xF5U, 0x16U, 0xEBU, 0x7AU, 0x75U, 0x2CU, 0xD7U,
0x4FU, 0xAEU, 0xD5U, 0xE9U, 0xE6U, 0xE7U, 0xADU, 0xE8U, 0x74U, 0xD6U, 0xF4U, 0xEAU, 0xA8U, 0x50U, 0x58U, 0xAFU};
/* multiplication using logarithms */
static unsigned char gmult(unsigned char a, unsigned char b)
{
if (a == 0U || b == 0U)
return 0U;
unsigned int i = LOG_TABLE[a];
unsigned int j = LOG_TABLE[b];
return EXP_TABLE[i + j];
}
/* Simulate a LFSR with generator polynomial for n byte RS code.
* Pass in a pointer to the data array, and amount of data.
*
* The parity bytes are deposited into parity.
*/
void CRS129::encode(const unsigned char* msg, unsigned int nbytes, unsigned char* parity)
{
assert(msg != NULL);
assert(parity != NULL);
for (unsigned int i = 0U; i < NPAR + 1U; i++)
parity[i] = 0x00U;
for (unsigned int i = 0U; i < nbytes; i++) {
unsigned char dbyte = msg[i] ^ parity[NPAR - 1U];
for (int j = NPAR - 1; j > 0; j--)
parity[j] = parity[j - 1] ^ ::gmult(POLY[j], dbyte);
parity[0] = ::gmult(POLY[0], dbyte);
}
}
// Reed-Solomon (12,9) check
bool CRS129::check(const unsigned char* in)
{
assert(in != NULL);
unsigned char parity[4U];
encode(in, 9U, parity);
return in[9U] == parity[2U] && in[10U] == parity[1U] && in[11U] == parity[0U];
}

@ -0,0 +1,30 @@
/*
* Copyright (C) 2015 by Jonathan Naylor G4KLX
*
* 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.
*/
#if !defined(RS129_H)
#define RS129_H
class CRS129
{
public:
static bool check(const unsigned char* in);
static void encode(const unsigned char* msg, unsigned int nbytes, unsigned char* parity);
};
#endif

@ -0,0 +1,610 @@
/*
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 <iostream>
#include <cstring>
#include "dcscodec.h"
#include "CRCenc.h"
#define DEBUG
const unsigned char MMDVM_DSTAR_HEADER = 0x10U;
const unsigned char MMDVM_DSTAR_DATA = 0x11U;
const unsigned char MMDVM_DSTAR_LOST = 0x12U;
const unsigned char MMDVM_DSTAR_EOT = 0x13U;
DCSCodec::DCSCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) :
Codec(callsign, module, hostname, host, port, ipv6, vocoder, modem, audioin, audioout)
{
}
DCSCodec::~DCSCodec()
{
}
void DCSCodec::process_udp()
{
QByteArray buf;
QHostAddress sender;
quint16 senderPort;
static bool sd_sync = 0;
static int sd_seq = 0;
static char user_data[21];
buf.resize(200);
//qDebug() << "buf size before == " << buf.size();
//buf.resize(m_udp->pendingDatagramSize());
//qDebug() << "buf size after == " << buf.size();
int size = m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
fprintf(stderr, "RECV: ");
for(int i = 0; i < size; ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
if(size == 22){ //2 way keep alive ping
m_modeinfo.count++;
m_modeinfo.netmsg.clear();
if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){
m_modeinfo.stream_state = STREAM_IDLE;
}
}
if( (m_modeinfo.status == CONNECTING) && (size == 14) && (!memcmp(buf.data()+10, "ACK", 3)) ){
qDebug() << "Connected to DCS";
m_modeinfo.status = CONNECTED_RW;
m_modeinfo.vocoder_loaded = load_vocoder_plugin();
if(m_vocoder != ""){
m_hwrx = true;
m_hwtx = true;
m_ambedev = new SerialAMBE("DCS");
m_ambedev->connect_to_serial(m_vocoder);
connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe()));
}
else{
m_hwrx = false;
m_hwtx = false;
}
if(m_modemport != ""){
m_modem = new SerialModem("DCS");
m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex);
m_modem->set_modem_params(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_modem->connect_to_serial(m_modemport);
connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray)));
}
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
m_txtimer = new QTimer();
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_ping_timer = new QTimer();
connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping()));
m_ping_timer->start(2000);
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
//fprintf(stderr, "m_vocoder == %s m_hwtx:m_hwrx == %d:%d\n", m_vocoder.toStdString().c_str(), m_hwtx, m_hwrx);fflush(stderr);
}
if(m_modeinfo.status != CONNECTED_RW) return;
if(size == 35){
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
m_modeinfo.netmsg = QString(buf.data());
}
if((size == 100) && (!memcmp(buf.data(), "0001", 4)) ){
m_rxwatchdog = 0;
//qDebug() << "m_streamid == " << m_streamid << ":" << m_hwrx << ":" << m_tx;
uint16_t streamid = (buf.data()[43] << 8) | (buf.data()[44] & 0xff);
if(!m_tx && (m_modeinfo.streamid == 0)){
m_modeinfo.streamid = streamid;
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
if(!m_rxtimer->isActive()){
m_audio->start_playback();
m_rxtimer->start(m_rxtimerint);
m_rxcodecq.clear();
}
char temp[9];
memcpy(temp, buf.data() + 7, 8); temp[8] = '\0';
m_modeinfo.gw2 = QString(temp);
memcpy(temp, buf.data() + 15, 8); temp[8] = '\0';
m_modeinfo.gw = QString(temp);
memcpy(temp, buf.data() + 23, 8); temp[8] = '\0';
m_modeinfo.dst = QString(temp);
memcpy(temp, buf.data() + 31, 8); temp[8] = '\0';
m_modeinfo.src = QString(temp);
QString h = m_hostname + " " + m_module;
if(m_modem){
uint8_t out[44];
out[0] = 0xe0;
out[1] = 44;
out[2] = MMDVM_DSTAR_HEADER;
out[3] = 0x40;
out[4] = 0;
out[5] = 0;
memcpy(out + 6, m_modeinfo.gw2.toLocal8Bit().data(), 8);
memcpy(out + 14, m_modeinfo.gw.toLocal8Bit().data(), 8);
memcpy(out + 22, m_modeinfo.dst.toLocal8Bit().data(), 8);
memcpy(out + 30, m_modeinfo.src.toLocal8Bit().data(), 8);
memcpy(out + 38, buf.data() + 52, 4);
CCRC::addCCITT161((uint8_t *)out + 3, 41);
for(int i = 0; i < 44; ++i){
m_rxmodemq.append(out[i]);
}
//m_modem->write(out);
}
qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16);
}
else{
m_modeinfo.stream_state = STREAMING;
}
m_modeinfo.frame_number = buf.data()[0x2d];
if((buf.data()[45] == 0) && (buf.data()[55] == 0x55) && (buf.data()[56] == 0x2d) && (buf.data()[57] == 0x16)){
sd_sync = 1;
sd_seq = 1;
}
if(sd_sync && (sd_seq == 1) && (buf.data()[45] == 1) && (buf.data()[55] == 0x30)){
user_data[0] = buf.data()[56] ^ 0x4f;
user_data[1] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 2) && (buf.data()[45] == 2)){
user_data[2] = buf.data()[55] ^ 0x70;
user_data[3] = buf.data()[56] ^ 0x4f;
user_data[4] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 3) && (buf.data()[45] == 3) && (buf.data()[55] == 0x31)){
user_data[5] = buf.data()[56] ^ 0x4f;
user_data[6] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 4) && (buf.data()[45] == 4)){
user_data[7] = buf.data()[55] ^ 0x70;
user_data[8] = buf.data()[56] ^ 0x4f;
user_data[9] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 5) && (buf.data()[45] == 5) && (buf.data()[55] == 0x32)){
user_data[10] = buf.data()[56] ^ 0x4f;
user_data[11] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 6) && (buf.data()[45] == 6)){
user_data[12] = buf.data()[55] ^ 0x70;
user_data[13] = buf.data()[56] ^ 0x4f;
user_data[14] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 7) && (buf.data()[45] == 7) && (buf.data()[55] == 0x33)){
user_data[15] = buf.data()[56] ^ 0x4f;
user_data[16] = buf.data()[57] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 8) && (buf.data()[45] == 8)){
user_data[17] = buf.data()[55] ^ 0x70;
user_data[18] = buf.data()[56] ^ 0x4f;
user_data[19] = buf.data()[57] ^ 0x93;
user_data[20] = '\0';
sd_sync = 0;
sd_seq = 0;
m_modeinfo.usertxt = QString(user_data);
}
if(buf.data()[45] & 0x40){
qDebug() << "DCS RX stream ended ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_END;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
if(m_modem){
m_rxmodemq.append(0xe0);
m_rxmodemq.append(3);
m_rxmodemq.append(MMDVM_DSTAR_EOT);
}
}
else if(m_modeinfo.stream_state == STREAMING){
if(m_modem){
m_rxmodemq.append(0xe0);
m_rxmodemq.append(15);
m_rxmodemq.append(MMDVM_DSTAR_DATA);
for(int i = 0; i < 12; ++i){
m_rxmodemq.append(buf.data()[46+i]);
}
}
}
for(int i = 0; i < 9; ++i){
m_rxcodecq.append(buf.data()[46+i]);
}
}
emit update(m_modeinfo);
}
void DCSCodec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
QByteArray out;
out.resize(519);
memcpy(out.data(), m_modeinfo.callsign.toStdString().c_str(), m_modeinfo.callsign.size());
memset(out.data() + m_modeinfo.callsign.size(), ' ', 8 - m_modeinfo.callsign.size());
out[8] = m_module;
out[9] = m_module;
out[10] = 11;
//out.append(m_modeinfo.callsign.toUtf8());
//out.append(8 - m_modeinfo.callsign.size(), ' ');
//out.append(m_module);
//out.append(m_module);
//out.append(11);
//out.append(508, 0);
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "CONN: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void DCSCodec::send_ping()
{
static QByteArray out;
out.clear();
out.append(m_modeinfo.callsign.toUtf8());
out.append(7 - m_modeinfo.callsign.size(), ' ');
out.append(m_module);
out.append('\x00');
out.append(m_hostname.toUtf8());
out.append('\x00');
out.append(m_module);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "PING: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void DCSCodec::send_disconnect()
{
QByteArray out;
out.append(m_modeinfo.callsign.toUtf8());
out.append(8 - m_modeinfo.callsign.size(), ' ');
out.append(m_module);
out.append(' ');
out.append('\x00');
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void DCSCodec::format_callsign(QString &s)
{
QStringList l = s.simplified().split(' ');
if(l.size() > 1){
s = l.at(0).simplified();
while(s.size() < 7){
s.append(' ');
}
s += l.at(1).simplified();
}
else{
while(s.size() < 8){
s.append(' ');
}
}
}
void DCSCodec::process_modem_data(QByteArray d)
{
QByteArray txdata;
char cs[9];
uint8_t ambe[9];
uint8_t *p_frame = (uint8_t *)(d.data());
if(p_frame[2] == MMDVM_DSTAR_HEADER){
format_callsign(m_txrptr1);
format_callsign(m_txrptr2);
cs[8] = 0;
memcpy(cs, p_frame + 22, 8);
m_txurcall = QString(cs);
memcpy(cs, p_frame + 30, 8);
m_txmycall = QString(cs);
m_modeinfo.stream_state = TRANSMITTING_MODEM;
m_tx = true;
}
else if( (p_frame[2] == MMDVM_DSTAR_EOT) || (p_frame[2] == MMDVM_DSTAR_LOST) ){
m_tx = false;
}
else if(p_frame[2] == MMDVM_DSTAR_DATA){
memcpy(ambe, p_frame + 3, 9);
}
send_frame(ambe);
}
void DCSCodec::toggle_tx(bool tx)
{
tx ? start_tx() : stop_tx();
}
void DCSCodec::start_tx()
{
format_callsign(m_txmycall);
format_callsign(m_txurcall);
format_callsign(m_txrptr1);
format_callsign(m_txrptr2);
Codec::start_tx();
}
void DCSCodec::transmit()
{
unsigned char ambe[9];
uint8_t ambe_frame[72];
int16_t pcm[160];
memset(ambe_frame, 0, 72);
memset(ambe, 0, 9);
#ifdef USE_FLITE
if(m_ttsid > 0){
for(int i = 0; i < 160; ++i){
if(m_ttscnt >= tts_audio->num_samples/2){
//audiotx_cnt = 0;
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[m_ttscnt*2] / 2;
m_ttscnt++;
}
}
}
#endif
if(m_ttsid == 0){
if(m_audio->read(pcm, 160)){
}
else{
return;
}
}
if(m_hwtx){
m_ambedev->encode(pcm);
if(m_tx && (m_txcodecq.size() >= 9)){
for(int i = 0; i < 9; ++i){
ambe[i] = m_txcodecq.dequeue();
}
send_frame(ambe);
}
else if(!m_tx){
send_frame(ambe);
}
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->encode_2400x1200(pcm, ambe);
}
send_frame(ambe);
}
}
void DCSCodec::send_frame(uint8_t *ambe)
{
QByteArray txdata;
static uint16_t txstreamid = 0;
txdata.clear();
txdata.append(100, 0);
if(txstreamid == 0){
txstreamid = static_cast<uint16_t>((::rand() & 0xFFFF));
}
txdata.replace(0, 4, "0001");
txdata.replace(7, 8, m_txrptr2.toLocal8Bit().data());
txdata.replace(15, 8, m_txrptr1.toLocal8Bit().data());
txdata.replace(23, 8, m_txurcall.toLocal8Bit().data());
txdata.replace(31, 8, m_txmycall.toLocal8Bit().data());
txdata.replace(39, 4, "AMBE");
txdata[43] = txstreamid & 0xff;
txdata[44] = (txstreamid >> 8) & 0xff;
txdata[45] = (m_txcnt % 21) & 0xff;
memcpy(txdata.data() + 46, ambe, 9);
switch(txdata.data()[45]){
case 0:
txdata[55] = 0x55;
txdata[56] = 0x2d;
txdata[57] = 0x16;
break;
case 1:
txdata[55] = 0x40 ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[0] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[1] ^ 0x93;
break;
case 2:
txdata[55] = m_txusrtxt.toLocal8Bit().data()[2] ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[3] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[4] ^ 0x93;
break;
case 3:
txdata[55] = 0x41 ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[5] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[6] ^ 0x93;
break;
case 4:
txdata[55] = m_txusrtxt.toLocal8Bit().data()[7] ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[8] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[9] ^ 0x93;
break;
case 5:
txdata[55] = 0x42 ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[10] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[11] ^ 0x93;
break;
case 6:
txdata[55] = m_txusrtxt.toLocal8Bit().data()[12] ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[13] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[14] ^ 0x93;
break;
case 7:
txdata[55] = 0x43 ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[15] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[16] ^ 0x93;
break;
case 8:
txdata[55] = m_txusrtxt.toLocal8Bit().data()[17] ^ 0x70;
txdata[56] = m_txusrtxt.toLocal8Bit().data()[18] ^ 0x4f;
txdata[57] = m_txusrtxt.toLocal8Bit().data()[19] ^ 0x93;
break;
default:
txdata[55] = 0x16;
txdata[56] = 0x29;
txdata[57] = 0xf5;
break;
}
txdata[58] = m_txcnt & 0xff;
txdata[59] = (m_txcnt >> 8) & 0xff;
txdata[60] = (m_txcnt >> 16) & 0xff;
txdata[61] = 0x01;
m_modeinfo.src = m_txmycall;
m_modeinfo.dst = m_txurcall;
m_modeinfo.gw = m_txrptr1;
m_modeinfo.gw2 = m_txrptr2;
m_modeinfo.streamid = txstreamid;
m_modeinfo.frame_number = m_txcnt;
if(m_tx){
m_txcnt++;
}
else{
uint8_t last_frame[9] = {0xdc, 0x8e, 0x0a, 0x40, 0xad, 0xed, 0xad, 0x39, 0x6e};
txdata[45] = (txdata[45] | 0x40);
txdata.replace(46, 9, (char *)last_frame);
m_txcnt = 0;
txstreamid = 0;
m_modeinfo.streamid = 0;
m_txtimer->stop();
if((m_ttsid == 0) && (m_modeinfo.stream_state == TRANSMITTING) ){
m_audio->stop_capture();
}
m_ttscnt = 0;
}
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
emit update_output_level(m_audio->level());
update(m_modeinfo);
#ifdef DEBUG
fprintf(stderr, "SEND:%d: ", txdata.size());
for(int i = 0; i < txdata.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void DCSCodec::get_ambe()
{
uint8_t ambe[9];
if(m_ambedev->get_ambe(ambe)){
for(int i = 0; i < 9; ++i){
m_txcodecq.append(ambe[i]);
}
}
}
void DCSCodec::process_rx_data()
{
int16_t pcm[160];
uint8_t ambe[9];
if(m_rxwatchdog++ > 100){
qDebug() << "DCS RX stream timeout ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_LOST;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
if(m_rxmodemq.size() > 2){
QByteArray out;
int s = m_rxmodemq[1];
if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){
for(int i = 0; i < s; ++i){
out.append(m_rxmodemq.dequeue());
}
m_modem->write(out);
}
}
if((!m_tx) && (m_rxcodecq.size() > 8) ){
for(int i = 0; i < 9; ++i){
ambe[i] = m_rxcodecq.dequeue();
}
if(m_hwrx){
m_ambedev->decode(ambe);
if(m_ambedev->get_audio(pcm)){
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->decode_2400x1200(pcm, ambe);
}
else{
memset(pcm, 0, 160 * sizeof(int16_t));
}
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){
m_rxtimer->stop();
m_audio->stop_playback();
m_rxwatchdog = 0;
m_modeinfo.streamid = 0;
m_rxcodecq.clear();
qDebug() << "DCS playback stopped";
return;
}
}

@ -0,0 +1,51 @@
/*
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/>.
*/
#ifndef DCSCODEC_H
#define DCSCODEC_H
#include "codec.h"
class DCSCodec : public Codec
{
Q_OBJECT
public:
DCSCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout);
~DCSCodec();
unsigned char * get_frame(unsigned char *ambe);
private:
QString m_txusrtxt;
uint8_t packet_size;
private slots:
void toggle_tx(bool);
void start_tx();
void process_udp();
void process_modem_data(QByteArray);
void process_rx_data();
void get_ambe();
void send_ping();
void send_disconnect();
void transmit();
void format_callsign(QString &s);
void hostname_lookup(QHostInfo i);
void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; }
void module_changed(int m) { m_module = 0x41 + m; m_modeinfo.streamid = 0; }
void usrtxt_changed(QString t) { m_txusrtxt = t; }
void send_frame(uint8_t *);
};
#endif // DCSCODEC_H

@ -0,0 +1,980 @@
/*
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 <iostream>
#include <cstring>
#include "dmrcodec.h"
#include "cgolay2087.h"
#include "crs129.h"
#include "SHA256.h"
#include "CRCenc.h"
#define DEBUG
const unsigned char MMDVM_DMR_DATA1 = 0x18U;
const unsigned char MMDVM_DMR_LOST1 = 0x19U;
const unsigned char MMDVM_DMR_DATA2 = 0x1AU;
const unsigned char MMDVM_DMR_LOST2 = 0x1BU;
const unsigned char MMDVM_DMR_SHORTLC = 0x1CU;
const unsigned char MMDVM_DMR_START = 0x1DU;
const unsigned char MMDVM_DMR_ABORT = 0x1EU;
const uint32_t ENCODING_TABLE_1676[] =
{0x0000U, 0x0273U, 0x04E5U, 0x0696U, 0x09C9U, 0x0BBAU, 0x0D2CU, 0x0F5FU, 0x11E2U, 0x1391U, 0x1507U, 0x1774U,
0x182BU, 0x1A58U, 0x1CCEU, 0x1EBDU, 0x21B7U, 0x23C4U, 0x2552U, 0x2721U, 0x287EU, 0x2A0DU, 0x2C9BU, 0x2EE8U,
0x3055U, 0x3226U, 0x34B0U, 0x36C3U, 0x399CU, 0x3BEFU, 0x3D79U, 0x3F0AU, 0x411EU, 0x436DU, 0x45FBU, 0x4788U,
0x48D7U, 0x4AA4U, 0x4C32U, 0x4E41U, 0x50FCU, 0x528FU, 0x5419U, 0x566AU, 0x5935U, 0x5B46U, 0x5DD0U, 0x5FA3U,
0x60A9U, 0x62DAU, 0x644CU, 0x663FU, 0x6960U, 0x6B13U, 0x6D85U, 0x6FF6U, 0x714BU, 0x7338U, 0x75AEU, 0x77DDU,
0x7882U, 0x7AF1U, 0x7C67U, 0x7E14U, 0x804FU, 0x823CU, 0x84AAU, 0x86D9U, 0x8986U, 0x8BF5U, 0x8D63U, 0x8F10U,
0x91ADU, 0x93DEU, 0x9548U, 0x973BU, 0x9864U, 0x9A17U, 0x9C81U, 0x9EF2U, 0xA1F8U, 0xA38BU, 0xA51DU, 0xA76EU,
0xA831U, 0xAA42U, 0xACD4U, 0xAEA7U, 0xB01AU, 0xB269U, 0xB4FFU, 0xB68CU, 0xB9D3U, 0xBBA0U, 0xBD36U, 0xBF45U,
0xC151U, 0xC322U, 0xC5B4U, 0xC7C7U, 0xC898U, 0xCAEBU, 0xCC7DU, 0xCE0EU, 0xD0B3U, 0xD2C0U, 0xD456U, 0xD625U,
0xD97AU, 0xDB09U, 0xDD9FU, 0xDFECU, 0xE0E6U, 0xE295U, 0xE403U, 0xE670U, 0xE92FU, 0xEB5CU, 0xEDCAU, 0xEFB9U,
0xF104U, 0xF377U, 0xF5E1U, 0xF792U, 0xF8CDU, 0xFABEU, 0xFC28U, 0xFE5BU};
DMRCodec::DMRCodec(QString callsign, uint32_t dmrid, uint8_t essid, QString password, QString lat, QString lon, QString location, QString desc, QString freq, QString url, QString swid, QString pkid, QString options, uint32_t dstid, QString host, uint32_t port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) :
Codec(callsign, 0, NULL, host, port, ipv6, vocoder, modem, audioin, audioout),
m_dmrid(dmrid),
m_password(password),
m_lat(lat),
m_lon(lon),
m_location(location),
m_desc(desc),
m_freq(freq),
m_url(url),
m_swid(swid),
m_pkid(pkid),
m_txdstid(dstid),
m_pc(false),
m_options(options)
{
m_dmrcnt = 0;
m_flco = FLCO(0);
if (essid){
m_essid = m_dmrid * 100 + (essid-1);
}
else{
m_essid = m_dmrid;
}
}
DMRCodec::~DMRCodec()
{
}
void DMRCodec::process_udp()
{
QByteArray buf;
QByteArray in;
QByteArray out;
QHostAddress sender;
quint16 senderPort;
CSHA256 sha256;
char buffer[400U];
buf.resize(m_udp->pendingDatagramSize());
m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
fprintf(stderr, "RECV: ");
for(int i = 0; i < buf.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
if((m_modeinfo.status != CONNECTED_RW) && (::memcmp(buf.data() + 3, "NAK", 3U) == 0)){
//m_udp->disconnect();
//m_udp->close();
//delete m_udp;
m_modeinfo.status = DISCONNECTED;
}
if((m_modeinfo.status != CONNECTED_RW) && (::memcmp(buf.data(), "MSTCL", 5U) == 0)){
//m_udp->disconnect();
//m_udp->close();
//delete m_udp;
m_modeinfo.status = CLOSED;
}
if((m_modeinfo.status != CONNECTED_RW) && (::memcmp(buf.data(), "RPTACK", 6U) == 0)){
switch(m_modeinfo.status){
case CONNECTING:
m_modeinfo.status = DMR_AUTH;
in.append(buf[6]);
in.append(buf[7]);
in.append(buf[8]);
in.append(buf[9]);
in.append(m_password.toUtf8());
out.clear();
out.resize(40);
out[0] = 'R';
out[1] = 'P';
out[2] = 'T';
out[3] = 'K';
out[4] = (m_essid >> 24) & 0xff;
out[5] = (m_essid >> 16) & 0xff;
out[6] = (m_essid >> 8) & 0xff;
out[7] = (m_essid >> 0) & 0xff;
sha256.buffer((unsigned char *)in.data(), (unsigned int)(m_password.size() + sizeof(uint32_t)), (unsigned char *)out.data() + 8U);
break;
case DMR_AUTH:
out.clear();
buffer[0] = 'R';
buffer[1] = 'P';
buffer[2] = 'T';
buffer[3] = 'C';
buffer[4] = (m_essid >> 24) & 0xff;
buffer[5] = (m_essid >> 16) & 0xff;
buffer[6] = (m_essid >> 8) & 0xff;
buffer[7] = (m_essid >> 0) & 0xff;
m_modeinfo.status = DMR_CONF;
char latitude[20U];
char longitude[20U];
sprintf(latitude, "%08f", m_lat.toFloat());
sprintf(longitude, "%09f", m_lon.toFloat());
char *p;
if((p = strchr(latitude, ',')) != NULL){
*p = '.';
}
if((p = strchr(longitude, ',')) != NULL){
*p = '.';
}
::sprintf(buffer + 8U, "%-8.8s%09u%09u%02u%02u%8.8s%9.9s%03d%-20.20s%-19.19s%c%-124.124s%-40.40s%-40.40s", m_modeinfo.callsign.toStdString().c_str(),
m_freq.toUInt(), m_freq.toUInt(), 1, 1, latitude, longitude, 0, m_location.toStdString().c_str(), m_desc.toStdString().c_str(), '4',
m_url.toStdString().c_str(), m_swid.toStdString().c_str(), m_pkid.toStdString().c_str());
out.append(buffer, 302);
break;
case DMR_CONF:
setup_connection();
if(m_options.size()){
out.clear();
out.append('R');
out.append('P');
out.append('T');
out.append('O');
out.append((m_essid >> 24) & 0xff);
out.append((m_essid >> 16) & 0xff);
out.append((m_essid >> 8) & 0xff);
out.append((m_essid >> 0) & 0xff);
out.append(m_options.toUtf8());
//m_status = DMR_OPTS;
}
break;
case DMR_OPTS:
//setup_connection();
break;
default:
break;
}
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
}
if((buf.size() == 11) && (::memcmp(buf.data(), "MSTPONG", 7U) == 0)){
m_modeinfo.count++;
}
if((buf.size() != 55) && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) )){
m_modeinfo.stream_state = STREAM_IDLE;
}
if((buf.size() == 55) &&
(::memcmp(buf.data(), "DMRD", 4U) == 0) &&
((uint8_t)buf.data()[15] & 0x20) &&
(m_modeinfo.status == CONNECTED_RW))
{
m_rxwatchdog = 0;
uint8_t t;
if((uint8_t)buf.data()[15] & 0x02){
qDebug() << "DMR RX EOT";
m_modeinfo.stream_state = STREAM_END;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
m_modeinfo.streamid = 0;
t = 0x42;
}
else if((uint8_t)buf.data()[15] & 0x01){
m_audio->start_playback();
if(!m_rxtimer->isActive()){
m_rxtimer->start(m_rxtimerint);
}
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
m_modeinfo.srcid = (uint32_t)((buf.data()[5] << 16) | ((buf.data()[6] << 8) & 0xff00) | (buf.data()[7] & 0xff));
m_modeinfo.dstid = (uint32_t)((buf.data()[8] << 16) | ((buf.data()[9] << 8) & 0xff00) | (buf.data()[10] & 0xff));
m_modeinfo.gwid = (uint32_t)((buf.data()[11] << 24) | ((buf.data()[12] << 16) & 0xff0000) | ((buf.data()[13] << 8) & 0xff00) | (buf.data()[14] & 0xff));
m_modeinfo.streamid = (uint32_t)((buf.data()[16] << 24) | ((buf.data()[17] << 16) & 0xff0000) | ((buf.data()[18] << 8) & 0xff00) | (buf.data()[19] & 0xff));
m_modeinfo.frame_number = buf.data()[4];
t = 0x41;
qDebug() << "New DMR stream from " << m_modeinfo.srcid << " to " << m_modeinfo.dstid;
}
if(m_modem){
m_rxmodemq.append(0xe0);
m_rxmodemq.append(0x25);
m_rxmodemq.append(MMDVM_DMR_DATA2);
m_rxmodemq.append(t);
for(int i = 0; i < 33; ++i){
m_rxmodemq.append(buf.data()[20+i]);
};
//m_rxmodemq.append('\x00');
//m_rxmodemq.append(0x2f);
}
}
if((buf.size() == 55) &&
(::memcmp(buf.data(), "DMRD", 4U) == 0) &&
!((uint8_t)buf.data()[15] & 0x20) &&
(m_modeinfo.status == CONNECTED_RW))
{
if(!m_tx && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_IDLE) )){
m_audio->start_playback();
if(!m_rxtimer->isActive()){
m_rxtimer->start(m_rxtimerint);
}
m_modeinfo.stream_state = STREAM_NEW;
}
else{
m_modeinfo.stream_state = STREAMING;
}
m_rxwatchdog = 0;
uint8_t dmrframe[33];
uint8_t dmr3ambe[27];
uint8_t dmrsync[7];
// get the 33 bytes ambe
memcpy(dmrframe, &(buf.data()[20]), 33);
// extract the 3 ambe frames
memcpy(dmr3ambe, dmrframe, 14);
dmr3ambe[13] &= 0xF0;
dmr3ambe[13] |= (dmrframe[19] & 0x0F);
memcpy(&dmr3ambe[14], &dmrframe[20], 13);
// extract sync
dmrsync[0] = dmrframe[13] & 0x0F;
::memcpy(&dmrsync[1], &dmrframe[14], 5);
dmrsync[6] = dmrframe[19] & 0xF0;
m_modeinfo.srcid = (uint32_t)((buf.data()[5] << 16) | ((buf.data()[6] << 8) & 0xff00) | (buf.data()[7] & 0xff));
m_modeinfo.dstid = (uint32_t)((buf.data()[8] << 16) | ((buf.data()[9] << 8) & 0xff00) | (buf.data()[10] & 0xff));
m_modeinfo.gwid = (uint32_t)((buf.data()[11] << 24) | ((buf.data()[12] << 16) & 0xff0000) | ((buf.data()[13] << 8) & 0xff00) | (buf.data()[14] & 0xff));
m_modeinfo.streamid = (uint32_t)((buf.data()[16] << 24) | ((buf.data()[17] << 16) & 0xff0000) | ((buf.data()[18] << 8) & 0xff00) | (buf.data()[19] & 0xff));
m_modeinfo.frame_number = buf.data()[4];
if(m_modem){
uint8_t t = ((uint8_t)buf.data()[15] & 0x0f);
if(!t) t = 0x20;
m_rxmodemq.append(0xe0);
m_rxmodemq.append(0x25);
m_rxmodemq.append(MMDVM_DMR_DATA2);
m_rxmodemq.append(t);
for(int i = 0; i < 33; ++i){
m_rxmodemq.append(buf.data()[20+i]);
}
}
for(int i = 0; i < 3; ++i){
for(int j = 0; j < 9; ++j){
m_rxcodecq.append(dmr3ambe[j + (9*i)]);
}
}
//uint32_t id = (uint32_t)((buf.data()[5] << 16) | ((buf.data()[6] << 8) & 0xff00) | (buf.data()[7] & 0xff));
}
emit update(m_modeinfo);
#ifdef DEBUG
if(out.size() > 0){
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
}
#endif
}
void DMRCodec::setup_connection()
{
m_modeinfo.status = CONNECTED_RW;
//m_mbeenc->set_gain_adjust(2.5);
m_modeinfo.vocoder_loaded = load_vocoder_plugin();
m_txtimer = new QTimer();
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
m_ping_timer = new QTimer();
connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping()));
m_ping_timer->start(5000);
if(m_vocoder != ""){
m_hwrx = true;
m_hwtx = true;
m_ambedev = new SerialAMBE("DMR");
m_ambedev->connect_to_serial(m_vocoder);
connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe()));
}
else{
m_hwrx = false;
m_hwtx = false;
}
if(m_modemport != ""){
m_modem = new SerialModem("DMR");
m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex);
m_modem->set_modem_params(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_modem->connect_to_serial(m_modemport);
connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray)));
}
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
}
void DMRCodec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
QByteArray out;
out.append('R');
out.append('P');
out.append('T');
out.append('L');
out.append((m_essid >> 24) & 0xff);
out.append((m_essid >> 16) & 0xff);
out.append((m_essid >> 8) & 0xff);
out.append((m_essid >> 0) & 0xff);
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "CONN: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void DMRCodec::send_ping()
{
QByteArray out;
char tag[] = { 'R','P','T','P','I','N','G' };
out.append(tag, 7);
out.append((m_essid >> 24) & 0xff);
out.append((m_essid >> 16) & 0xff);
out.append((m_essid >> 8) & 0xff);
out.append((m_essid >> 0) & 0xff);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "PING: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void DMRCodec::send_disconnect()
{
QByteArray out;
out.append('R');
out.append('P');
out.append('T');
out.append('C');
out.append('L');
out.append((m_essid >> 24) & 0xff);
out.append((m_essid >> 16) & 0xff);
out.append((m_essid >> 8) & 0xff);
out.append((m_essid >> 0) & 0xff);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void DMRCodec::process_modem_data(QByteArray d)
{
QByteArray txdata;
uint8_t lcData[12U];
uint8_t *p_frame = (uint8_t *)(d.data());
m_dataType = p_frame[3U] & 0x0f;
if ((p_frame[3U] & DMR_SYNC_DATA) == DMR_SYNC_DATA){
if((m_dataType == DT_VOICE_LC_HEADER) && (m_modeinfo.stream_state == STREAM_IDLE)){
m_modeinfo.stream_state = TRANSMITTING_MODEM;
//qDebug() << "ids == " << m_txsrcid << ":" << m_txdstid;
}
else if(m_dataType == DT_TERMINATOR_WITH_LC){
m_modeinfo.stream_state = STREAM_IDLE;
}
m_dmrcnt = 0;
m_bptc.decode(p_frame + 4, lcData);
m_txdstid = lcData[3U] << 16 | lcData[4U] << 8 | lcData[5U];
m_txsrcid = lcData[6U] << 16 | lcData[7U] << 8 | lcData[8U];
m_flco = FLCO(lcData[0U] & 0x3FU);
build_frame();
::memcpy(m_dmrFrame + 20U, p_frame + 4, 33U);
txdata.append((char *)m_dmrFrame, 55);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
}
else {
m_dataType = (m_dmrcnt % 6U) ? DT_VOICE : DT_VOICE_SYNC;
build_frame();
::memcpy(m_dmrFrame + 20U, p_frame + 4, 33U);
txdata.append((char *)m_dmrFrame, 55);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
++m_dmrcnt;
}
#ifdef DEBUG
fprintf(stderr, "SEND:%d: ", txdata.size());
for(int i = 0; i < txdata.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void DMRCodec::transmit()
{
uint8_t ambe[72];
int16_t pcm[160];
#ifdef USE_FLITE
if(m_ttsid > 0){
for(int i = 0; i < 160; ++i){
if(m_ttscnt >= tts_audio->num_samples/2){
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[m_ttscnt*2] / 2;
m_ttscnt++;
}
}
}
#endif
if(m_ttsid == 0){
if(m_audio->read(pcm, 160)){
}
else{
return;
}
}
if(m_hwtx){
m_ambedev->encode(pcm);
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->encode_2450x1150(pcm, ambe);
}
for(int i = 0; i < 9; ++i){
m_txcodecq.append(ambe[i]);
}
}
if(m_tx && (m_txcodecq.size() >= 27)){
for(int i = 0; i < 27; ++i){
m_ambe[i] = m_txcodecq.dequeue();
}
send_frame();
}
else if(m_tx == false){
send_frame();
}
}
void DMRCodec::send_frame()
{
QByteArray txdata;
if(m_pc){
set_calltype(3);
}
else{
set_calltype(0);
}
m_txsrcid = m_dmrid;
if(m_tx){
m_modeinfo.stream_state = TRANSMITTING;
if(!m_dmrcnt){
encode_header(DT_VOICE_LC_HEADER);
m_txstreamid = static_cast<uint32_t>(::rand());
}
else{
::memcpy(m_dmrFrame + 20U, m_ambe, 13U);
m_dmrFrame[33U] = m_ambe[13U] & 0xF0U;
m_dmrFrame[39U] = m_ambe[13U] & 0x0FU;
::memcpy(m_dmrFrame + 40U, &m_ambe[14U], 13U);
encode_data();
}
build_frame();
txdata.append((char *)m_dmrFrame, 55);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
++m_dmrcnt;
/*
if(!m_dmrcnt){
for (int i = 0U; i < 3; i++) {
m_dmrFrame[4U] = m_dmrcnt;
txdata.append((char *)m_dmrFrame, 55);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
m_dmrcnt++;
}
}
else{
++m_dmrcnt;
txdata.append((char *)m_dmrFrame, 55);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
}
*/
}
else{
//fprintf(stderr, "DMR TX stopped\n");
get_eot();
build_frame();
m_ttscnt = 0;
txdata.append((char *)m_dmrFrame, 55);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
m_txtimer->stop();
if(m_ttsid == 0){
m_audio->stop_capture();
}
m_modeinfo.stream_state = STREAM_IDLE;
}
emit update_output_level(m_audio->level());
emit update(m_modeinfo);
#ifdef DEBUG
fprintf(stderr, "SEND:%d: ", txdata.size());
for(int i = 0; i < txdata.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
unsigned char * DMRCodec::get_eot()
{
encode_header(DT_TERMINATOR_WITH_LC);
m_dmrcnt = 0;
return m_dmrFrame;
}
void DMRCodec::build_frame()
{
m_dmrFrame[0U] = 'D';
m_dmrFrame[1U] = 'M';
m_dmrFrame[2U] = 'R';
m_dmrFrame[3U] = 'D';
m_dmrFrame[5U] = m_txsrcid >> 16;
m_dmrFrame[6U] = m_txsrcid >> 8;
m_dmrFrame[7U] = m_txsrcid >> 0;
m_dmrFrame[8U] = m_txdstid >> 16;
m_dmrFrame[9U] = m_txdstid >> 8;
m_dmrFrame[10U] = m_txdstid >> 0;
m_dmrFrame[11U] = m_essid >> 24;
m_dmrFrame[12U] = m_essid >> 16;
m_dmrFrame[13U] = m_essid >> 8;
m_dmrFrame[14U] = m_essid >> 0;
m_dmrFrame[15U] = (m_slot == 1U) ? 0x00U : 0x80U;
m_dmrFrame[15U] |= (m_flco == FLCO_GROUP) ? 0x00U : 0x40U;
if (m_dataType == DT_VOICE_SYNC) {
m_dmrFrame[15U] |= 0x10U;
} else if (m_dataType == DT_VOICE) {
m_dmrFrame[15U] |= ((m_dmrcnt - 1) % 6U);
} else {
m_dmrFrame[15U] |= (0x20U | m_dataType);
}
m_dmrFrame[4U] = m_dmrcnt;
::memcpy(m_dmrFrame + 16U, &m_txstreamid, 4U);
m_dmrFrame[53U] = 0; //data.getBER();
m_dmrFrame[54U] = 0; //data.getRSSI();
m_modeinfo.srcid = m_txsrcid;
m_modeinfo.dstid = m_txdstid;
m_modeinfo.gwid = m_essid;
m_modeinfo.frame_number = m_dmrcnt;
}
void DMRCodec::encode_header(uint8_t t)
{
addDMRDataSync(m_dmrFrame+20, 0);
m_dataType = t;
full_lc_encode(m_dmrFrame+20, t);
}
void DMRCodec::encode_data()
{
unsigned int n_dmr = (m_dmrcnt - 1) % 6U;
if (!n_dmr) {
m_dataType = DT_VOICE_SYNC;
addDMRAudioSync(m_dmrFrame+20, 0);
encode_embedded_data();
}
else {
m_dataType = DT_VOICE;
uint8_t lcss = get_embedded_data(m_dmrFrame+20, n_dmr);
get_emb_data(m_dmrFrame+20, lcss);
}
}
void DMRCodec::encode16114(bool* d)
{
d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8];
d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9];
d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10];
d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10];
d[15] = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10];
}
void DMRCodec::encode_qr1676(uint8_t* data)
{
uint32_t value = (data[0U] >> 1) & 0x7FU;
uint32_t cksum = ENCODING_TABLE_1676[value];
data[0U] = cksum >> 8;
data[1U] = cksum & 0xFFU;
}
void DMRCodec::get_emb_data(uint8_t* data, uint8_t lcss)
{
uint8_t DMREMB[2U];
DMREMB[0U] = (m_colorcode << 4) & 0xF0U;
//DMREMB[0U] |= m_PI ? 0x08U : 0x00U;
DMREMB[0U] |= (lcss << 1) & 0x06U;
DMREMB[1U] = 0x00U;
encode_qr1676(DMREMB);
data[13U] = (data[13U] & 0xF0U) | ((DMREMB[0U] >> 4U) & 0x0FU);
data[14U] = (data[14U] & 0x0FU) | ((DMREMB[0U] << 4U) & 0xF0U);
data[18U] = (data[18U] & 0xF0U) | ((DMREMB[1U] >> 4U) & 0x0FU);
data[19U] = (data[19U] & 0x0FU) | ((DMREMB[1U] << 4U) & 0xF0U);
}
uint8_t DMRCodec::get_embedded_data(uint8_t* data, uint8_t n)
{
if (n >= 1U && n < 5U) {
n--;
bool bits[40U];
::memset(bits, 0x00U, 40U * sizeof(bool));
::memcpy(bits + 4U, m_raw + n * 32U, 32U * sizeof(bool));
uint8_t bytes[5U];
bitsToByteBE(bits + 0U, bytes[0U]);
bitsToByteBE(bits + 8U, bytes[1U]);
bitsToByteBE(bits + 16U, bytes[2U]);
bitsToByteBE(bits + 24U, bytes[3U]);
bitsToByteBE(bits + 32U, bytes[4U]);
data[14U] = (data[14U] & 0xF0U) | (bytes[0U] & 0x0FU);
data[15U] = bytes[1U];
data[16U] = bytes[2U];
data[17U] = bytes[3U];
data[18U] = (data[18U] & 0x0FU) | (bytes[4U] & 0xF0U);
switch (n) {
case 0U:
return 1U;
case 3U:
return 2U;
default:
return 3U;
}
} else {
data[14U] &= 0xF0U;
data[15U] = 0x00U;
data[16U] = 0x00U;
data[17U] = 0x00U;
data[18U] &= 0x0FU;
return 0U;
}
}
void DMRCodec::encode_embedded_data()
{
uint32_t crc;
lc_get_data(m_data);
CCRC::encodeFiveBit(m_data, crc);
bool data[128U];
::memset(data, 0x00U, 128U * sizeof(bool));
data[106U] = (crc & 0x01U) == 0x01U;
data[90U] = (crc & 0x02U) == 0x02U;
data[74U] = (crc & 0x04U) == 0x04U;
data[58U] = (crc & 0x08U) == 0x08U;
data[42U] = (crc & 0x10U) == 0x10U;
uint32_t b = 0U;
for (uint32_t a = 0U; a < 11U; a++, b++)
data[a] = m_data[b];
for (uint32_t a = 16U; a < 27U; a++, b++)
data[a] = m_data[b];
for (uint32_t a = 32U; a < 42U; a++, b++)
data[a] = m_data[b];
for (uint32_t a = 48U; a < 58U; a++, b++)
data[a] = m_data[b];
for (uint32_t a = 64U; a < 74U; a++, b++)
data[a] = m_data[b];
for (uint32_t a = 80U; a < 90U; a++, b++)
data[a] = m_data[b];
for (uint32_t a = 96U; a < 106U; a++, b++)
data[a] = m_data[b];
// Hamming (16,11,4) check each row except the last one
for (uint32_t a = 0U; a < 112U; a += 16U)
encode16114(data + a);
// Add the parity bits for each column
for (uint32_t a = 0U; a < 16U; a++)
data[a + 112U] = data[a + 0U] ^ data[a + 16U] ^ data[a + 32U] ^ data[a + 48U] ^ data[a + 64U] ^ data[a + 80U] ^ data[a + 96U];
// The data is packed downwards in columns
b = 0U;
for (uint32_t a = 0U; a < 128U; a++) {
m_raw[a] = data[b];
b += 16U;
if (b > 127U)
b -= 127U;
}
}
void DMRCodec::bitsToByteBE(const bool* bits, uint8_t& byte)
{
byte = bits[0U] ? 0x80U : 0x00U;
byte |= bits[1U] ? 0x40U : 0x00U;
byte |= bits[2U] ? 0x20U : 0x00U;
byte |= bits[3U] ? 0x10U : 0x00U;
byte |= bits[4U] ? 0x08U : 0x00U;
byte |= bits[5U] ? 0x04U : 0x00U;
byte |= bits[6U] ? 0x02U : 0x00U;
byte |= bits[7U] ? 0x01U : 0x00U;
}
void DMRCodec::byteToBitsBE(uint8_t byte, bool* bits)
{
bits[0U] = (byte & 0x80U) == 0x80U;
bits[1U] = (byte & 0x40U) == 0x40U;
bits[2U] = (byte & 0x20U) == 0x20U;
bits[3U] = (byte & 0x10U) == 0x10U;
bits[4U] = (byte & 0x08U) == 0x08U;
bits[5U] = (byte & 0x04U) == 0x04U;
bits[6U] = (byte & 0x02U) == 0x02U;
bits[7U] = (byte & 0x01U) == 0x01U;
}
void DMRCodec::lc_get_data(bool* bits)
{
uint8_t bytes[9U];
memset(bytes, 0, 9);
lc_get_data(bytes);
byteToBitsBE(bytes[0U], bits + 0U);
byteToBitsBE(bytes[1U], bits + 8U);
byteToBitsBE(bytes[2U], bits + 16U);
byteToBitsBE(bytes[3U], bits + 24U);
byteToBitsBE(bytes[4U], bits + 32U);
byteToBitsBE(bytes[5U], bits + 40U);
byteToBitsBE(bytes[6U], bits + 48U);
byteToBitsBE(bytes[7U], bits + 56U);
byteToBitsBE(bytes[8U], bits + 64U);
}
void DMRCodec::lc_get_data(uint8_t *bytes)
{
bool pf, r;
uint8_t fid, options;
bytes[0U] = (uint8_t)m_flco;
pf = (bytes[0U] & 0x80U) == 0x80U;
r = (bytes[0U] & 0x40U) == 0x40U;
//m_flco = FLCO(bytes[0U] & 0x3FU);
fid = bytes[1U];
options = bytes[2U];
//bytes[0U] = (uint8_t)m_flco;
if (pf)
bytes[0U] |= 0x80U;
if (r)
bytes[0U] |= 0x40U;
bytes[1U] = fid;
bytes[2U] = options;
bytes[3U] = m_txdstid >> 16;
bytes[4U] = m_txdstid >> 8;
bytes[5U] = m_txdstid >> 0;
bytes[6U] = m_dmrid >> 16;
bytes[7U] = m_dmrid >> 8;
bytes[8U] = m_dmrid >> 0;
}
void DMRCodec::full_lc_encode(uint8_t* data, uint8_t type) // for header
{
uint8_t lcData[12U];
uint8_t parity[4U];
::memset(lcData, 0, sizeof(lcData));
lc_get_data(lcData);
CRS129::encode(lcData, 9U, parity);
switch (type) {
case DT_VOICE_LC_HEADER:
lcData[9U] = parity[2U] ^ VOICE_LC_HEADER_CRC_MASK[0U];
lcData[10U] = parity[1U] ^ VOICE_LC_HEADER_CRC_MASK[1U];
lcData[11U] = parity[0U] ^ VOICE_LC_HEADER_CRC_MASK[2U];
break;
case DT_TERMINATOR_WITH_LC:
lcData[9U] = parity[2U] ^ TERMINATOR_WITH_LC_CRC_MASK[0U];
lcData[10U] = parity[1U] ^ TERMINATOR_WITH_LC_CRC_MASK[1U];
lcData[11U] = parity[0U] ^ TERMINATOR_WITH_LC_CRC_MASK[2U];
break;
default:
return;
}
get_slot_data(data);
m_bptc.encode(lcData, data);
}
void DMRCodec::get_slot_data(uint8_t* data)
{
uint8_t DMRSlotType[3U];
DMRSlotType[0U] = (m_colorcode << 4) & 0xF0U;
DMRSlotType[0U] |= (m_dataType << 0) & 0x0FU;
DMRSlotType[1U] = 0x00U;
DMRSlotType[2U] = 0x00U;
CGolay2087::encode(DMRSlotType);
data[12U] = (data[12U] & 0xC0U) | ((DMRSlotType[0U] >> 2) & 0x3FU);
data[13U] = (data[13U] & 0x0FU) | ((DMRSlotType[0U] << 6) & 0xC0U) | ((DMRSlotType[1U] >> 2) & 0x30U);
data[19U] = (data[19U] & 0xF0U) | ((DMRSlotType[1U] >> 2) & 0x0FU);
data[20U] = (data[20U] & 0x03U) | ((DMRSlotType[1U] << 6) & 0xC0U) | ((DMRSlotType[2U] >> 2) & 0x3CU);
}
void DMRCodec::addDMRDataSync(uint8_t* data, bool duplex)
{
if (duplex) {
for (uint32_t i = 0U; i < 7U; i++)
data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | BS_SOURCED_DATA_SYNC[i];
} else {
for (uint32_t i = 0U; i < 7U; i++)
data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | MS_SOURCED_DATA_SYNC[i];
}
}
void DMRCodec::addDMRAudioSync(uint8_t* data, bool duplex)
{
if (duplex) {
for (uint32_t i = 0U; i < 7U; i++)
data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | BS_SOURCED_AUDIO_SYNC[i];
} else {
for (uint32_t i = 0U; i < 7U; i++)
data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | MS_SOURCED_AUDIO_SYNC[i];
}
}
void DMRCodec::get_ambe()
{
uint8_t ambe[9];
if(m_ambedev->get_ambe(ambe)){
for(int i = 0; i < 9; ++i){
m_txcodecq.append(ambe[i]);
}
}
}
void DMRCodec::process_rx_data()
{
int16_t pcm[160];
uint8_t ambe[9];
if(m_rxwatchdog++ > 100){
qDebug() << "DMR RX stream timeout ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_LOST;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
if(m_rxmodemq.size() > 2){
QByteArray out;
int s = m_rxmodemq[1];
if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){
for(int i = 0; i < s; ++i){
out.append(m_rxmodemq.dequeue());
}
m_modem->write(out);
}
}
if((!m_tx) && (m_rxcodecq.size() > 8) ){
for(int i = 0; i < 9; ++i){
ambe[i] = m_rxcodecq.dequeue();
}
if(m_hwrx){
m_ambedev->decode(ambe);
if(m_ambedev->get_audio(pcm)){
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->decode_2450x1150(pcm, ambe);
}
else{
memset(pcm, 0, 160 * sizeof(int16_t));
}
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){
m_rxtimer->stop();
m_audio->stop_playback();
m_rxwatchdog = 0;
m_modeinfo.streamid = 0;
m_rxcodecq.clear();
qDebug() << "DMR playback stopped";
return;
}
}

@ -0,0 +1,99 @@
/*
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/>.
*/
#ifndef DMRCODEC_H
#define DMRCODEC_H
#include "codec.h"
//#include <inttypes.h>
#include "DMRData.h"
#include "cbptc19696.h"
class DMRCodec : public Codec
{
Q_OBJECT
public:
DMRCodec(QString callsign, uint32_t dmrid, uint8_t essid, QString password, QString lat, QString lon, QString location, QString desc, QString freq, QString url, QString swid, QString pkid, QString options, uint32_t dstid, QString host, uint32_t port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout);
~DMRCodec();
unsigned char * get_eot();
void set_cc(uint32_t cc){m_colorcode = cc;}
void set_slot(uint32_t s){m_slot = s;}
void set_calltype(uint8_t c){m_flco = FLCO(c);}
private slots:
void process_udp();
void process_rx_data();
void process_modem_data(QByteArray);
void get_ambe();
void send_ping();
void send_disconnect();
void transmit();
void hostname_lookup(QHostInfo i);
void dmr_tgid_changed(unsigned int id) { m_txdstid = id; }
void dmr_cc_changed(int cc) {m_colorcode = cc + 1; }
void dmr_slot_changed(int s) {m_slot = s + 1; }
void dmrpc_state_changed(int pc){ m_pc = (pc ? true : false); }
void send_frame();
private:
uint32_t m_dmrid;
uint32_t m_essid;
QString m_password;
QString m_lat;
QString m_lon;
QString m_location;
QString m_desc;
QString m_freq;
QString m_url;
QString m_swid;
QString m_pkid;
uint32_t m_txsrcid;
uint32_t m_txdstid;
uint32_t m_txstreamid;
uint8_t packet_size;
uint8_t m_ambe[27];
uint32_t m_defsrcid;
uint8_t m_dmrFrame[55];
uint8_t m_dataType;
uint32_t m_colorcode;
uint32_t m_slot;
uint32_t m_dmrcnt;
bool m_pc;
FLCO m_flco;
CBPTC19696 m_bptc;
bool m_raw[128U];
bool m_data[72U];
QString m_options;
void byteToBitsBE(uint8_t byte, bool* bits);
void bitsToByteBE(const bool* bits, uint8_t& byte);
void build_frame();
void encode_header(uint8_t);
void encode_data();
void encode16114(bool* d);
void encode_qr1676(uint8_t* data);
void get_slot_data(uint8_t* data);
void lc_get_data(uint8_t*);
void lc_get_data(bool* bits);
void encode_embedded_data();
uint8_t get_embedded_data(uint8_t* data, uint8_t n);
void get_emb_data(uint8_t* data, uint8_t lcss);
void full_lc_encode(uint8_t* data, uint8_t type);
void addDMRDataSync(uint8_t* data, bool duplex);
void addDMRAudioSync(uint8_t* data, bool duplex);
void setup_connection();
};
#endif // DMRCODEC_H

File diff suppressed because it is too large Load Diff

@ -0,0 +1,385 @@
/*
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/>.
*/
#ifndef DROIDSTAR_H
#define DROIDSTAR_H
#include <QObject>
#include <QTimer>
#include "refcodec.h"
#include "dcscodec.h"
#include "xrfcodec.h"
#include "ysfcodec.h"
#include "dmrcodec.h"
#include "p25codec.h"
#include "nxdncodec.h"
#include "m17codec.h"
#include "iaxcodec.h"
class DroidStar : public QObject
{
Q_OBJECT
//Q_PROPERTY(QString mode READ mode WRITE set_mode NOTIFY mode_changed)
public:
explicit DroidStar(QObject *parent = nullptr);
~DroidStar();
signals:
void mode_changed();
void module_changed(char);
void update_data();
void update_log(QString);
void update_settings();
void connect_status_changed(int c);
void in_audio_vol_changed(qreal);
void tx_pressed();
void tx_released();
void tx_clicked(bool);
void dmrpc_state_changed(int);
void dmr_tgid_changed(unsigned int);
void m17_rate_changed(int);
void send_dtmf(QByteArray);
void swtx_state_changed(int);
void swrx_state_changed(int);
void swtx_state(int);
void swrx_state(int);
void agc_state(int);
void agc_state_changed(int);
void rptr1_changed(QString);
void rptr2_changed(QString);
void mycall_changed(QString);
void urcall_changed(QString);
public slots:
void set_callsign(const QString &callsign) { m_callsign = callsign.simplified(); save_settings(); }
void set_dmrtgid(const QString &dmrtgid) { m_dmr_destid = dmrtgid.simplified().toUInt(); save_settings(); }
void tgid_text_changed(QString s){ qDebug() << "dmrid_text_changed() called s == " << s; emit dmr_tgid_changed(s.toUInt());}
void set_dmrid(const QString &dmrid) { m_dmrid = dmrid.simplified().toUInt(); save_settings(); }
void set_essid(const QString &essid)
{
if (essid != "None") {
m_essid = essid.simplified().toUInt() + 1;
save_settings();
}
else{
m_essid = 0;
}
}
void set_bm_password(const QString &bmpwd) { m_bm_password = bmpwd; save_settings(); }
void set_tgif_password(const QString &tgifpwd) { m_tgif_password = tgifpwd; save_settings(); }
void set_latitude(const QString &lat){ m_latitude = lat; save_settings(); }
void set_longitude(const QString &lon){ m_longitude = lon; save_settings(); }
void set_location(const QString &loc){ m_location = loc; save_settings(); }
void set_description(const QString &desc){ m_description = desc; save_settings(); }
void set_freq(const QString &freq){ m_freq = freq; save_settings(); }
void set_url(const QString &url){ m_url = url; save_settings(); }
void set_swid(const QString &swid){ m_swid = swid; save_settings(); }
void set_pkgid(const QString &pkgid){ m_pkgid = pkgid; save_settings(); }
void set_dmr_options(const QString &dmropts) { m_dmropts = dmropts; save_settings(); }
void set_dmr_pc(int pc) { emit dmrpc_state_changed(pc); }
//void set_host(const QString &host) { m_host = host; save_settings(); }
void set_module(const QString &module) { m_module = module.toStdString()[0]; save_settings(); emit module_changed(m_module);}
void set_protocol(const QString &protocol) { m_protocol = protocol; save_settings(); }
void set_input_volume(qreal v);
void set_modelchange(bool t){ m_modelchange = t; }
void set_iaxuser(const QString &user){ m_iaxuser = user; save_settings(); }
void set_iaxpass(const QString &pass){ m_iaxpassword = pass; save_settings(); }
void set_iaxnode(const QString &node){ m_iaxnode = node; save_settings(); }
void set_iaxhost(const QString &host){ m_iaxhost = host; save_settings(); }
void set_mycall(const QString &mycall) { m_mycall = mycall; save_settings(); emit mycall_changed(mycall); }
void set_urcall(const QString &urcall) { m_urcall = urcall; save_settings(); emit urcall_changed(urcall); }
void set_rptr1(const QString &rptr1) { m_rptr1 = rptr1; save_settings(); emit rptr1_changed(rptr1); }
void set_rptr2(const QString &rptr2) { m_rptr2 = rptr2; save_settings(); emit rptr2_changed(rptr2); }
void set_txtimeout(const QString &t) { m_txtimeout = t.simplified().toUInt(); save_settings();}
void set_toggletx(bool x) { m_toggletx = x; save_settings(); }
void set_xrf2ref(bool x) { m_xrf2ref = x; save_settings(); }
void set_ipv6(bool ipv6) { m_ipv6 = ipv6; save_settings(); }
void set_vocoder(QString vocoder) { m_vocoder = vocoder; }
void set_modem(QString modem) { m_modem = modem; }
void set_playback(QString playback) { m_playback = playback; }
void set_capture(QString capture) { m_capture = capture; }
void set_swtx(bool swtx) { emit swtx_state_changed(swtx); }
void set_swrx(bool swrx) { emit swrx_state_changed(swrx); }
void set_agc(bool agc) { emit agc_state_changed(agc); }
void set_iaxport(const QString &port){ m_iaxport = port.simplified().toUInt(); save_settings(); }
void set_modemRxFreq(QString m) { m_modemRxFreq = m; save_settings(); }
void set_modemTxFreq(QString m) { m_modemTxFreq = m; save_settings(); }
void set_modemRxOffset(QString m) { m_modemRxOffset = m; save_settings(); }
void set_modemTxOffset(QString m) { m_modemTxOffset = m; save_settings(); }
void set_modemRxDCOffset(QString m) { m_modemRxDCOffset = m; save_settings(); }
void set_modemTxDCOffset(QString m) { m_modemTxDCOffset = m; save_settings(); }
void set_modemRxLevel(QString m) { m_modemRxLevel = m; save_settings(); }
void set_modemTxLevel(QString m) { m_modemTxLevel = m; save_settings(); }
void set_modemRFLevel(QString m) { m_modemRFLevel = m; save_settings(); }
void set_modemTxDelay(QString m) { m_modemTxDelay = m; save_settings(); }
void set_modemCWIdTxLevel(QString m) { m_modemCWIdTxLevel = m; save_settings(); }
void set_modemDstarTxLevel(QString m) { m_modemDstarTxLevel = m; save_settings(); }
void set_modemDMRTxLevel(QString m) { m_modemDMRTxLevel = m; save_settings(); }
void set_modemYSFTxLevel(QString m) { m_modemYSFTxLevel = m; save_settings(); }
void set_modemP25TxLevel(QString m) { m_modemP25TxLevel = m; save_settings(); }
void set_modemNXDNTxLevel(QString m) { m_modemNXDNTxLevel = m; save_settings(); }
void m17_rate_changed(bool r) { qDebug() << "m17_rate_changed() r == " << r; emit m17_rate_changed((int)r); }
void process_connect();
void press_tx();
void release_tx();
void click_tx(bool);
void process_settings();
void check_host_files();
void update_host_files();
void update_custom_hosts(QString);
void update_dmr_ids();
void update_nxdn_ids();
void process_mode_change(const QString &m);
void process_host_change(const QString &h);
void dtmf_send_clicked(QString);
bool get_modelchange(){ return m_modelchange; }
QString get_label1() { return m_label1; }
QString get_label2() { return m_label2; }
QString get_label3() { return m_label3; }
QString get_label4() { return m_label4; }
QString get_label5() { return m_label5; }
QString get_label6() { return m_label6; }
QString get_data1() { return m_data1; }
QString get_data2() { return m_data2; }
QString get_data3() { return m_data3; }
QString get_data4() { return m_data4; }
QString get_data5() { return m_data5; }
QString get_data6() { return m_data6; }
QString get_statustxt() { return m_statustxt; }
QString get_mode() { return m_protocol; }
QString get_host() { return m_host; }
QString get_module() { return QString(m_module); }
QString get_callsign() { return m_callsign; }
QString get_dmrid() { return m_dmrid ? QString::number(m_dmrid) : ""; }
QString get_essid() { return m_essid ? QString("%1").arg(m_essid - 1, 2, 10, QChar('0')) : "None"; }
QString get_bm_password() { return m_bm_password; }
QString get_tgif_password() { return m_tgif_password; }
QString get_latitude() { return m_latitude; }
QString get_longitude() { return m_longitude; }
QString get_location() { return m_location; }
QString get_description() { return m_description; }
QString get_freq() { return m_freq; }
QString get_url() { return m_url; }
QString get_swid() { return m_swid; }
QString get_pkgid() { return m_pkgid; }
QString get_dmr_options() { return m_dmropts; }
QString get_dmrtgid() { return m_dmr_destid ? QString::number(m_dmr_destid) : ""; }
QStringList get_hosts() { return m_hostsmodel; }
QString get_ref_host() { return m_saved_refhost; }
QString get_dcs_host() { return m_saved_dcshost; }
QString get_xrf_host() { return m_saved_xrfhost; }
QString get_ysf_host() { return m_saved_ysfhost; }
QString get_fcs_host() { return m_saved_fcshost; }
QString get_dmr_host() { return m_saved_dmrhost; }
QString get_p25_host() { return m_saved_p25host; }
QString get_nxdn_host() { return m_saved_nxdnhost; }
QString get_m17_host() { return m_saved_m17host; }
QString get_iax_host() { return m_saved_iaxhost; }
QString get_iax_user() { return m_iaxuser; }
QString get_iax_pass() { return m_iaxpassword; }
QString get_iax_node() { return m_iaxnode; }
QString get_iax_port() { return QString::number(m_iaxport); }
QString get_mycall() { return m_mycall; }
QString get_urcall() { return m_urcall; }
QString get_rptr1() { return m_rptr1; }
QString get_rptr2() { return m_rptr2; }
QString get_txtimeout() { return QString::number(m_txtimeout); }
QString get_error_text() { return m_errortxt; }
bool get_toggletx() { return m_toggletx; }
bool get_ipv6() { return m_ipv6; }
bool get_xrf2ref() { return m_xrf2ref; }
QString get_local_hosts(){ return m_localhosts; }
QStringList get_vocoders() { return m_vocoders; }
QStringList get_modems() { return m_modems; }
QStringList get_playbacks() { return m_playbacks; }
QStringList get_captures() { return m_captures; }
QString get_modemRxFreq() { return m_modemRxFreq; }
QString get_modemTxFreq() { return m_modemTxFreq; }
QString get_modemRxOffset() { return m_modemRxOffset; }
QString get_modemTxOffset() { return m_modemTxOffset; }
QString get_modemRxDCOffset() { return m_modemRxDCOffset; }
QString get_modemTxDCOffset() { return m_modemTxDCOffset; }
QString get_modemRxLevel() { return m_modemRxLevel; }
QString get_modemTxLevel() { return m_modemTxLevel; }
QString get_modemRFLevel() { return m_modemRFLevel; }
QString get_modemTxDelay() { return m_modemTxDelay; }
QString get_modemCWIdTxLevel() { return m_modemCWIdTxLevel; }
QString get_modemDstarTxLevel() { return m_modemDstarTxLevel; }
QString get_modemDMRTxLevel() { return m_modemDMRTxLevel; }
QString get_modemYSFTxLevel() { return m_modemYSFTxLevel; }
QString get_modemP25TxLevel() { return m_modemP25TxLevel; }
QString get_modemNXDNTxLevel() { return m_modemNXDNTxLevel; }
#if defined(Q_OS_ANDROID)
QString get_platform() { return QSysInfo::productType(); }
#else
QString get_platform() { return QSysInfo::kernelType(); }
#endif
QString get_arch() { return QSysInfo::currentCpuArchitecture(); }
QString get_build_abi() { return QSysInfo::buildAbi(); }
QString get_software_build() { return VERSION_NUMBER; }
void download_file(QString);
void file_downloaded(QString);
unsigned short get_output_level(){ return m_outlevel; }
void set_output_level(unsigned short l){ m_outlevel = l; }
private:
int connect_status;
bool m_update_host_files;
QSettings *m_settings;
QString config_path;
QString hosts_filename;
QString m_callsign;
QString m_host;
QString m_hostname;
QString m_protocol;
QString m_bm_password;
QString m_tgif_password;
QString m_latitude;
QString m_longitude;
QString m_location;
QString m_description;
QString m_freq;
QString m_url;
QString m_swid;
QString m_pkgid;
QString m_dmropts;
QString m_saved_refhost;
QString m_saved_dcshost;
QString m_saved_xrfhost;
QString m_saved_ysfhost;
QString m_saved_fcshost;
QString m_saved_dmrhost;
QString m_saved_p25host;
QString m_saved_nxdnhost;
QString m_saved_m17host;
QString m_saved_iaxhost;
uint32_t m_dmrid;
uint8_t m_essid;
uint32_t m_dmr_srcid;
uint32_t m_dmr_destid;
QMap<uint32_t, QString> m_dmrids;
QMap<uint16_t, QString> m_nxdnids;
char m_module;
int m_port;
QString m_label1;
QString m_label2;
QString m_label3;
QString m_label4;
QString m_label5;
QString m_label6;
QString m_data1;
QString m_data2;
QString m_data3;
QString m_data4;
QString m_data5;
QString m_data6;
QString m_statustxt;
QString m_mycall;
QString m_urcall;
QString m_rptr1;
QString m_rptr2;
int m_txtimeout;
bool m_toggletx;
QString m_dstarusertxt;
QStringList m_hostsmodel;
QMap<QString, QString> m_hostmap;
QStringList m_customhosts;
QThread *m_modethread;
REFCodec *m_ref;
DCSCodec *m_dcs;
XRFCodec *m_xrf;
YSFCodec *m_ysf;
DMRCodec *m_dmr;
P25Codec *m_p25;
NXDNCodec *m_nxdn;
M17Codec *m_m17;
IAXCodec *m_iax;
QByteArray user_data;
QString m_iaxuser;
QString m_iaxpassword;
QString m_iaxnode;
QString m_iaxhost;
QString m_localhosts;
int m_iaxport;
bool m_settings_processed;
bool m_modelchange;
const unsigned char header[5] = {0x80,0x44,0x53,0x56,0x54}; //DVSI packet header
uint16_t m_outlevel;
QString m_errortxt;
bool m_xrf2ref;
bool m_ipv6;
QString m_vocoder;
QString m_modem;
QString m_playback;
QString m_capture;
QStringList m_vocoders;
QStringList m_modems;
QStringList m_playbacks;
QStringList m_captures;
QString m_modemRxFreq;
QString m_modemTxFreq;
QString m_modemRxOffset;
QString m_modemTxOffset;
QString m_modemRxDCOffset;
QString m_modemTxDCOffset;
QString m_modemRxLevel;
QString m_modemTxLevel;
QString m_modemRFLevel;
QString m_modemTxDelay;
QString m_modemCWIdTxLevel;
QString m_modemDstarTxLevel;
QString m_modemDMRTxLevel;
QString m_modemYSFTxLevel;
QString m_modemP25TxLevel;
QString m_modemNXDNTxLevel;
bool m_modemTxInvert;
bool m_modemRxInvert;
bool m_modemPTTInvert;
private slots:
#ifdef Q_OS_ANDROID
void keepScreenOn();
#endif
void discover_devices();
void process_ref_hosts();
void process_dcs_hosts();
void process_xrf_hosts();
void process_ysf_hosts();
void process_fcs_rooms();
void process_dmr_hosts();
void process_p25_hosts();
void process_nxdn_hosts();
void process_m17_hosts();
void process_dmr_ids();
void process_nxdn_ids();
void update_dmr_data(Codec::MODEINFO);
void update_ref_data(Codec::MODEINFO);
void update_dcs_data(Codec::MODEINFO);
void update_xrf_data(Codec::MODEINFO);
void update_nxdn_data(Codec::MODEINFO);
void update_p25_data(Codec::MODEINFO);
void update_ysf_data(Codec::MODEINFO);
void update_m17_data(Codec::MODEINFO);
void update_iax_data();
void save_settings();
void update_output_level(unsigned short l){ m_outlevel = l;}
//void load_md380_fw();
};
#endif // DROIDSTAR_H

@ -0,0 +1,66 @@
/*
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 "httpmanager.h"
HttpManager::HttpManager(QString f) : QObject(nullptr)
{
m_qnam = new QNetworkAccessManager(this);
QObject::connect(m_qnam, SIGNAL(finished(QNetworkReply*)), this, SLOT(http_finished(QNetworkReply*)));
m_config_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WIN)
m_config_path += "/dudetronics";
#endif
m_filename = f;
}
void HttpManager::process()
{
QMetaObject::invokeMethod(this,"doRequest");
//qDebug() << "process() called";
//send to the event loop
}
void HttpManager::doRequest()
{
m_qnam->get(QNetworkRequest(QUrl("http://www.dudetronics.com/ar-dns" + m_filename)));
//qDebug() << "doRequest() called m_filename == " << m_filename;
}
void HttpManager::http_finished(QNetworkReply *reply)
{
qDebug() << "http_finished() called";
if (reply->error()) {
reply->deleteLater();
reply = nullptr;
qDebug() << "http_finished() error()";
return;
}
else{
QFile *hosts_file = new QFile(m_config_path + m_filename);
hosts_file->open(QIODevice::WriteOnly);
QFileInfo fileInfo(hosts_file->fileName());
QString filename(fileInfo.fileName());
hosts_file->write(reply->readAll());
hosts_file->flush();
hosts_file->close();
delete hosts_file;
emit file_downloaded(filename);
//fprintf(stderr, "Downloaded %s\n", filename.toStdString().c_str());fflush(stderr);
}
QThread::currentThread()->quit();
}

@ -0,0 +1,45 @@
/*
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/>.
*/
#ifndef HTTPMANAGER_H
#define HTTPMANAGER_H
#include <QObject>
#include <QtNetwork>
class HttpManager : public QObject
{
Q_OBJECT
public:
explicit HttpManager(QString);
//void start_request(QString file);
signals:
void file_downloaded(QString);
private:
QString m_filename;
QString m_config_path;
QNetworkAccessManager *m_qnam;
private slots:
void process();
void doRequest();
void http_finished(QNetworkReply *reply);
};
#endif // HTTPMANAGER_H

@ -0,0 +1,840 @@
/*
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 "iaxcodec.h"
#include "iaxdefines.h"
#ifdef Q_OS_WIN
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#define DEBUG
#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
IAXCodec::IAXCodec(QString callsign, QString username, QString password, QString node, QString host, int port, QString audioin, QString audioout) :
m_callsign(callsign),
m_username(username),
m_password(password),
m_node(node),
m_host(host),
m_port(port),
m_scallno(0),
m_dcallno(0),
m_regscallno(0x7fff),
m_regdcallno(0),
m_audioin(audioin),
m_audioout(audioout),
m_iseq(0),
m_oseq(0),
m_tx(false),
m_rxjitter(0),
m_rxloss(1),
m_rxframes(0),
m_rxdelay(0),
m_rxdropped(0),
m_rxooo(0),
m_ttsid(0),
m_cnt(0)
{
#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
QStringList l = m_node.split('@');
if(l.size() == 2){
m_node = l.at(0).simplified();
m_context = l.at(1).simplified();
}
else{
m_context = "iax-client";
}
}
IAXCodec::~IAXCodec()
{
}
int16_t ulaw_decode(int8_t number)
{
const uint16_t MULAW_BIAS = 33;
uint8_t sign = 0, position = 0;
int16_t decoded = 0;
number = ~number;
if (number & 0x80)
{
number &= ~(1 << 7);
sign = -1;
}
position = ((number & 0xF0) >> 4) + 5;
decoded = ((1 << position) | ((number & 0x0F) << (position - 4))
| (1 << (position - 5))) - MULAW_BIAS;
return (sign == 0) ? (decoded) : (-(decoded));
}
int8_t ulaw_encode(int16_t number)
{
const uint16_t MULAW_MAX = 0x1FFF;
const uint16_t MULAW_BIAS = 33;
uint16_t mask = 0x1000;
uint8_t sign = 0;
uint8_t position = 12;
uint8_t lsb = 0;
if (number < 0)
{
number = -number;
sign = 0x80;
}
number += MULAW_BIAS;
if (number > MULAW_MAX)
{
number = MULAW_MAX;
}
for (; ((number & mask) != mask && position >= 5); mask >>= 1, position--)
;
lsb = (number >> (position - 4)) & 0x0f;
return (~(sign | ((position - 5) << 4) | lsb));
}
void IAXCodec::send_call()
{
uint16_t scall = htons(++m_scallno | 0x8000);
m_oseq = m_iseq = 0;
QByteArray out;
out.append((char *)&scall, 2);
out.append('\x00');
out.append('\x00');
out.append('\x00');
out.append('\x00');
out.append('\x00');
out.append('\x00');
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_NEW);
out.append(IAX_IE_VERSION);
out.append(sizeof(short));
out.append('\x00');
out.append(IAX_PROTO_VERSION);
out.append(IAX_IE_CALLED_NUMBER);
out.append(m_node.size());
out.append(m_node.toUtf8(), m_node.size());
out.append(IAX_IE_CALLING_NUMBER);
out.append('\x00');
out.append(IAX_IE_CALLING_NAME);
out.append(m_callsign.size());
out.append(m_callsign.toUtf8(), m_callsign.size());
out.append(IAX_IE_USERNAME);
out.append(m_username.size());
out.append(m_username.toUtf8(), m_username.size());
out.append(IAX_IE_FORMAT);
out.append(sizeof(int));
out.append('\x00');
out.append('\x00');
out.append('\x00');
out.append(AST_FORMAT_ULAW);
m_timestamp = QDateTime::currentMSecsSinceEpoch();
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_call_auth()
{
QByteArray out;
m_md5seed.append(m_password.toUtf8());
QByteArray result = QCryptographicHash::hash(m_md5seed, QCryptographicHash::Md5);
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_AUTHREP);
out.append(IAX_IE_MD5_RESULT);
out.append(result.toHex().size());
out.append(result.toHex());
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_dtmf(QByteArray dtmf)
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
//uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp) + 3);
for(int i = 0; i < dtmf.size(); ++i){
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp) + 3 + (i*3));
out.clear();
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq+i);
out.append(m_iseq);
out.append(AST_FRAME_DTMF);
out.append(dtmf.data()[i]);
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void IAXCodec::send_radio_key(bool key)
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
out.clear();
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_CONTROL);
out.append(key ? AST_CONTROL_KEY : AST_CONTROL_UNKEY);
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_ping()
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_PING);
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_pong()
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
uint32_t jitter = htonl(m_rxjitter);
//uint32_t loss = htonl(m_rxloss);
uint32_t frames = htonl(m_rxframes);
uint16_t delay = htons(m_rxdelay);
uint32_t dropped = htonl(m_rxdropped);
uint32_t ooo = htonl(m_rxooo);
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_PONG);
out.append(IAX_IE_RR_JITTER);
out.append(sizeof(jitter));
out.append((char *)&jitter, sizeof(jitter));
out.append(IAX_IE_RR_LOSS);
out.append(sizeof(m_rxloss));
out.append((char *)&m_rxloss, sizeof(m_rxloss));
out.append(IAX_IE_RR_PKTS);
out.append(sizeof(frames));
out.append((char *)&frames, sizeof(frames));
out.append(IAX_IE_RR_DELAY);
out.append(sizeof(delay));
out.append((char *)&delay, sizeof(delay));
out.append(IAX_IE_RR_DROPPED);
out.append(sizeof(dropped));
out.append((char *)&dropped, sizeof(dropped));
out.append(IAX_IE_RR_OOO);
out.append(sizeof(ooo));
out.append((char *)&ooo, sizeof(ooo));
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_ack(uint16_t scall, uint16_t dcall, uint8_t oseq, uint8_t iseq)
{
QByteArray out;
scall = htons(scall | 0x8000);
dcall = htons(dcall);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(oseq);
out.append(iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_ACK);
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_lag_response()
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_LAGRP);
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_voice_frame(int16_t *f)
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_VOICE);
out.append(AST_FORMAT_ULAW);
for(int i = 0; i < 160; ++i){
out.append(ulaw_encode(f[i]));
}
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_registration(uint16_t dcall)
{
//static qint64 time = QDateTime::currentMSecsSinceEpoch();
uint32_t ts;
uint8_t seq;
if(dcall){
dcall = htons(dcall);
seq = 1;
ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
}
else{
--m_regscallno;
seq = 0;
//ts = htonl(3);
ts = 0;
m_md5seed.clear();
}
uint16_t scall = htons(m_regscallno | 0x8000);
uint16_t refresh = htons(60);
QByteArray out;
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(seq);
out.append(seq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_REGREQ);
out.append(IAX_IE_USERNAME);
out.append(m_username.size());
out.append(m_username.toUtf8(), m_username.size());
if(dcall){
m_md5seed.append(m_password.toUtf8());
QByteArray result = QCryptographicHash::hash(m_md5seed, QCryptographicHash::Md5);
out.append(IAX_IE_MD5_RESULT);
out.append(result.toHex().size());
out.append(result.toHex());
}
out.append(IAX_IE_REFRESH);
out.append(0x02);
out.append((char *)&refresh, 2); // refresh time = 60 secs
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::send_disconnect()
{
QByteArray out;
uint16_t scall = htons(m_scallno | 0x8000);
uint16_t dcall = htons(m_dcallno);
uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3);
QString bye("BuhBye Dudesters");
out.append((char *)&scall, 2);
out.append((char *)&dcall, 2);
out.append((char *)&ts, 4);
out.append(m_oseq);
out.append(m_iseq);
out.append(AST_FRAME_IAX);
out.append(IAX_COMMAND_HANGUP);
out.append(IAX_IE_CAUSE);
out.append(bye.size());
out.append(bye.toUtf8(), bye.size());
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
m_regtimer = new QTimer();
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
connect(m_regtimer, SIGNAL(timeout()), this, SLOT(send_registration()));
m_timestamp = QDateTime::currentMSecsSinceEpoch();
send_registration(0);
m_regtimer->start(60000);
}
}
void IAXCodec::send_connect()
{
m_status = CONNECTING;
qDebug() << "lookup IP = " << m_host << ":" << m_port;
QHostInfo::lookupHost(m_host, this, SLOT(hostname_lookup(QHostInfo)));
}
void IAXCodec::process_udp()
{
QByteArray buf;
QHostAddress sender;
quint16 senderPort;
buf.resize(m_udp->pendingDatagramSize());
m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
if(buf.data()[0] & 0x80){
fprintf(stderr, "RECV: ");
for(int i = 0; i < buf.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
}
#endif
if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_REGAUTH) &&
(buf.data()[12] == IAX_IE_AUTHMETHODS) &&
((buf.data()[15] & 0x02) == IAX_AUTH_MD5) &&
(buf.data()[16] == IAX_IE_CHALLENGE) )
{
uint16_t dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_md5seed.clear();
m_md5seed.append(buf.mid(18, buf.data()[17]));
send_registration(dcallno);
}
else if((buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_REGACK) )
{
uint16_t dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
uint16_t scallno = (((buf.data()[2] & 0x7f) << 8) | ((uint8_t)buf.data()[3]));
send_ack(scallno, dcallno, 2, 2);
if(m_status == CONNECTING){
send_call();
}
}
else if((buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_REGREJ) )
{
m_status = DISCONNECTED;
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_AUTHREQ) &&
(buf.data()[12] == IAX_IE_AUTHMETHODS) &&
(buf.data()[15] == IAX_AUTH_MD5) &&
(buf.data()[16] == IAX_IE_CHALLENGE) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_md5seed.clear();
m_md5seed.append(buf.mid(18, buf.data()[17]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_call_auth();
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_ACK) )
{
uint16_t scall = ((buf.data()[2] << 8) | ((uint8_t)buf.data()[3]));
if(scall == m_scallno){
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
}
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_ACCEPT) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_REJECT) )
{
m_status = DISCONNECTED;
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_CONTROL) &&
(buf.data()[11] == AST_CONTROL_RINGING) )
{
//int16_t zeropcm[160];
//memset(zeropcm, 0, 160 * sizeof(int16_t));
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
//send_voice_frame(zeropcm);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_CONTROL) &&
(buf.data()[11] == AST_CONTROL_ANSWER) )
{
if(m_status == CONNECTING){
m_status = CONNECTED_RW;
m_txtimer = new QTimer();
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
m_rxtimer->start(19);
m_pingtimer = new QTimer();
connect(m_pingtimer, SIGNAL(timeout()), this, SLOT(send_ping()));
m_pingtimer->start(2000);
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
m_audio->start_playback();
m_audio->set_input_buffer_size(640);
m_audio->start_capture();
//m_txtimer->start(19);
}
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_PING) )
{
++m_rxframes;
++m_cnt;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
send_pong();
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_PONG) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_VNAK) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_VOICE) &&
(buf.data()[11] == AST_FORMAT_ULAW) )
{
int16_t zeropcm[160];
memset(zeropcm, 0, 160 * sizeof(int16_t));
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
for(int i = 0; i < buf.size() - 12; ++i){
m_audioq.append(ulaw_decode(buf.data()[12+i]));
}
send_voice_frame(zeropcm);
if(!m_txtimer->isActive()){
m_txtimer->start(19);
}
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_TEXT) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_CONTROL) &&
(buf.data()[11] == AST_CONTROL_OPTION) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
}
else if( (buf.data()[0] & 0x80) &&
(buf.data()[10] == AST_FRAME_IAX) &&
(buf.data()[11] == IAX_COMMAND_LAGRQ) )
{
++m_rxframes;
m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1]));
m_iseq = buf.data()[8] + 1;
m_oseq = buf.data()[9];
send_ack(m_scallno, m_dcallno, m_oseq, m_iseq);
send_lag_response();
}
else if(!(buf.data()[0] & 0x80)){
uint16_t dcallno = ((buf.data()[0] << 8) | ((uint8_t)buf.data()[1]));
if(dcallno == m_dcallno){
for(int i = 0; i < buf.size() - 4; ++i){
m_audioq.append(ulaw_decode(buf.data()[4+i]));
}
}
}
emit update();
}
void IAXCodec::process_rx_data()
{
int16_t pcm[160];
if(m_audioq.size() > 160){
for(int i = 0; i < 160; ++i){
pcm[i] = (qreal)m_audioq.dequeue(); // * m_rxgain;
}
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
else return;
}
void IAXCodec::toggle_tx(bool tx)
{
qDebug() << "IAXCodec::toggle_tx(bool tx) == " << tx;
tx ? start_tx() : stop_tx();
}
void IAXCodec::start_tx()
{
//std::cerr << "Pressed TX buffersize == " << audioin->bufferSize() << std::endl;
//QByteArray tx("*99", 3);
//send_dtmf(tx);
send_radio_key(true);
m_ttscnt = 0;
qDebug() << "start_tx() " << m_ttsid << " " << m_ttstext;
m_tx = true;
#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
}
void IAXCodec::stop_tx()
{
m_tx = false;
send_radio_key(false);
//QByteArray tx("#", 1);
//send_dtmf(tx);
}
void IAXCodec::transmit()
{
QByteArray out;
int16_t pcm[160];
uint16_t s = 0;
#ifdef USE_FLITE
if(m_ttsid > 0){
m_audio->read(pcm);
s = 160;
if(m_tx){
for(int i = 0; i < 160; ++i){
if(m_ttscnt >= tts_audio->num_samples/2){
//audiotx_cnt = 0;
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[m_ttscnt*2] / 2;
m_ttscnt++;
}
}
}
}
#endif
if(m_ttsid == 0){
s = m_audio->read(pcm);
}
if (s == 0) return;
uint16_t scall = htons(m_scallno);
uint16_t ts = htons( (QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3 );
out.append((char *)&scall, 2);
out.append((char *)&ts, 2);
for(int i = 0; i < s; ++i){
out.append(ulaw_encode(pcm[i]));
}
m_udp->writeDatagram(out, m_address, m_port);
#ifdef DEBUGG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void IAXCodec::deleteLater()
{
if(m_status == CONNECTED_RW){
m_udp->disconnect();
m_txtimer->stop();
m_rxtimer->stop();
m_regtimer->stop();
send_disconnect();
//delete m_audio;
}
//m_cnt = 0;
QObject::deleteLater();
}

@ -0,0 +1,124 @@
/*
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/>.
*/
#ifndef IAXCODEC_H
#define IAXCODEC_H
#include <QObject>
#include <QtNetwork>
#include "audioengine.h"
#ifdef USE_FLITE
#include <flite/flite.h>
#endif
class IAXCodec : public QObject
{
Q_OBJECT
public:
IAXCodec(QString callsign, QString username, QString password, QString node, QString host, int port, QString audioin, QString audioout);
~IAXCodec();
uint8_t get_status(){ return m_status; }
QString get_host() { return m_host; }
int get_port() { return m_port; }
int get_cnt() { return m_cnt; }
void set_input_src(uint8_t s, QString t) { m_ttsid = s; m_ttstext = t; }
signals:
void update();
void update_output_level(unsigned short);
private slots:
void deleteLater();
void process_udp();
void send_connect();
void send_disconnect();
void hostname_lookup(QHostInfo i);
void send_registration(uint16_t dcall = 0);
void send_call();
void send_call_auth();
void send_ack(uint16_t, uint16_t, uint8_t, uint8_t);
void send_lag_response();
void send_ping();
void send_pong();
void toggle_tx(bool);
void start_tx();
void stop_tx();
void transmit();
void process_rx_data();
void send_voice_frame(int16_t *);
void send_dtmf(QByteArray);
void send_radio_key(bool);
void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; }
void in_audio_vol_changed(qreal v){ m_audio->set_input_volume(v); }
void out_audio_vol_changed(qreal v){ m_audio->set_output_volume(v); }
private:
enum{
DISCONNECTED,
CLOSED,
CONNECTING,
DMR_AUTH,
DMR_CONF,
DMR_OPTS,
CONNECTED_RW,
CONNECTED_RO
} m_status;
QUdpSocket *m_udp = nullptr;
QHostAddress m_address;
QString m_callsign;
QString m_username;
QString m_password;
QString m_node;
QString m_context;
QString m_host;
int m_port;
uint16_t m_scallno;
uint16_t m_dcallno;
uint16_t m_regscallno;
uint16_t m_regdcallno;
int m_id;
QString m_audioin;
QString m_audioout;
qint64 m_timestamp;
uint8_t m_regstat;
QByteArray m_md5seed;
QTimer *m_regtimer;
QTimer *m_pingtimer;
QTimer *m_rxtimer;
QTimer *m_txtimer;
AudioEngine *m_audio;
uint8_t m_iseq;
uint8_t m_oseq;
QQueue<int16_t> m_audioq;
bool m_tx;
uint32_t m_rxjitter;
uint32_t m_rxloss;
uint32_t m_rxframes;
uint16_t m_rxdelay;
uint32_t m_rxdropped;
uint32_t m_rxooo;
uint8_t m_ttsid;
QString m_ttstext;
uint16_t m_ttscnt;
int m_cnt;
//qreal m_rxgain;
#ifdef USE_FLITE
cst_voice *voice_slt;
cst_voice *voice_kal;
cst_voice *voice_awb;
cst_wave *tts_audio;
#endif
};
#endif // IAXCODEC_H

@ -0,0 +1,85 @@
/*
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/>.
These macros were derived from the iaxclient library
https://www.voip-info.org/iaxclient/
*/
#ifndef IAXDEFINES_H
#define IAXDEFINES_H
#define IAX_PROTO_VERSION 2
#define AST_FRAME_DTMF 1
#define AST_FRAME_VOICE 2
#define AST_FRAME_CONTROL 4
#define AST_FRAME_IAX 6
#define AST_FRAME_TEXT 7
#define AST_CONTROL_HANGUP 1
#define AST_CONTROL_RING 2
#define AST_CONTROL_RINGING 3
#define AST_CONTROL_ANSWER 4
#define AST_CONTROL_OPTION 11
#define AST_CONTROL_KEY 12
#define AST_CONTROL_UNKEY 13
#define AST_FORMAT_ULAW 4
#define IAX_AUTH_MD5 2
#define IAX_COMMAND_NEW 1
#define IAX_COMMAND_PING 2
#define IAX_COMMAND_PONG 3
#define IAX_COMMAND_ACK 4
#define IAX_COMMAND_HANGUP 5
#define IAX_COMMAND_REJECT 6
#define IAX_COMMAND_ACCEPT 7
#define IAX_COMMAND_AUTHREQ 8
#define IAX_COMMAND_AUTHREP 9
#define IAX_COMMAND_INVAL 10
#define IAX_COMMAND_LAGRQ 11
#define IAX_COMMAND_LAGRP 12
#define IAX_COMMAND_REGREQ 13
#define IAX_COMMAND_REGAUTH 14
#define IAX_COMMAND_REGACK 15
#define IAX_COMMAND_REGREJ 16
#define IAX_COMMAND_VNAK 18
#define IAX_IE_CALLED_NUMBER 1
#define IAX_IE_CALLING_NUMBER 2
#define IAX_IE_CALLING_NAME 4
#define IAX_IE_CALLED_CONTEXT 5
#define IAX_IE_USERNAME 6
#define IAX_IE_PASSWORD 7
#define IAX_IE_CAPABILITY 8
#define IAX_IE_FORMAT 9
#define IAX_IE_VERSION 11
#define IAX_IE_DNID 13
#define IAX_IE_AUTHMETHODS 14
#define IAX_IE_CHALLENGE 15
#define IAX_IE_MD5_RESULT 16
#define IAX_IE_APPARENT_ADDR 18
#define IAX_IE_REFRESH 19
#define IAX_IE_CAUSE 22
#define IAX_IE_DATETIME 31
#define IAX_IE_RR_JITTER 46
#define IAX_IE_RR_LOSS 47
#define IAX_IE_RR_PKTS 48
#define IAX_IE_RR_DELAY 49
#define IAX_IE_RR_DROPPED 50
#define IAX_IE_RR_OOO 51
#endif // IAXDEFINES_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

@ -0,0 +1,491 @@
/*
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 <cstring>
#include <iostream>
#include "m17codec.h"
#define M17CHARACTERS " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."
#define DEBUG
M17Codec::M17Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout) :
Codec(callsign, module, hostname, host, port, ipv6, NULL, modem, audioin, audioout),
m_txrate(1)
{
m_modeinfo.callsign = callsign;
m_modeinfo.host = host;
m_modeinfo.port = port;
m_modeinfo.count = 0;
m_modeinfo.frame_number = 0;
m_modeinfo.streamid = 0;
#ifdef Q_OS_WIN
m_txtimerint = 30; // Qt timers on windows seem to be slower than desired value
#else
m_txtimerint = 36;
#endif
}
M17Codec::~M17Codec()
{
}
void M17Codec::encode_callsign(uint8_t *callsign)
{
const std::string m17_alphabet(M17CHARACTERS);
char cs[10];
memset(cs, 0, sizeof(cs));
memcpy(cs, callsign, strlen((char *)callsign));
uint64_t encoded = 0;
for(int i = std::strlen((char *)callsign)-1; i >= 0; i--) {
auto pos = m17_alphabet.find(cs[i]);
if (pos == std::string::npos) {
pos = 0;
}
encoded *= 40;
encoded += pos;
}
for (int i=0; i<6; i++) {
callsign[i] = (encoded >> (8*(5-i)) & 0xFFU);
}
}
void M17Codec::decode_callsign(uint8_t *callsign)
{
const std::string m17_alphabet(M17CHARACTERS);
uint8_t code[6];
uint64_t coded = callsign[0];
for (int i=1; i<6; i++)
coded = (coded << 8) | callsign[i];
if (coded > 0xee6b27ffffffu) {
std::cerr << "Callsign code is too large, 0x" << std::hex << coded << std::endl;
return;
}
memcpy(code, callsign, 6);
memset(callsign, 0, 10);
int i = 0;
while (coded) {
if(i < 10){
callsign[i++] = m17_alphabet[coded % 40];
}
coded /= 40;
}
}
void M17Codec::decode_c2(int16_t *audio, uint8_t *c)
{
m_c2->codec2_decode(audio, c);
}
void M17Codec::encode_c2(int16_t *audio, uint8_t *c)
{
m_c2->codec2_encode(c, audio);
}
void M17Codec::process_udp()
{
QByteArray buf;
QHostAddress sender;
quint16 senderPort;
buf.resize(m_udp->pendingDatagramSize());
m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
fprintf(stderr, "RECV: ");
for(int i = 0; i < buf.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
if((m_modeinfo.status != CONNECTED_RW) && (buf.size() == 4) && (::memcmp(buf.data(), "NACK", 4U) == 0)){
m_modeinfo.status = DISCONNECTED;
}
if((buf.size() == 4) && (::memcmp(buf.data(), "ACKN", 4U) == 0)){
if(m_modeinfo.status == CONNECTING){
m_modeinfo.status = CONNECTED_RW;
m_c2 = new CCodec2(true);
m_txtimer = new QTimer();
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
m_ping_timer = new QTimer();
connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping()));
m_ping_timer->start(8000);
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
}
emit update(m_modeinfo);
}
if((buf.size() == 10) && (::memcmp(buf.data(), "PING", 4U) == 0)){
if(m_modeinfo.streamid == 0){
m_modeinfo.stream_state = STREAM_IDLE;
}
m_modeinfo.count++;
emit update(m_modeinfo);
}
if((buf.size() == 54) && (::memcmp(buf.data(), "M17 ", 4U) == 0)){
uint16_t streamid = (buf.data()[4] << 8) | (buf.data()[5] & 0xff);
if( (m_modeinfo.streamid != 0) && (streamid != m_modeinfo.streamid) ){
qDebug() << "New streamid received before timeout";
m_modeinfo.streamid = 0;
m_audio->stop_playback();
}
if( !m_tx && (m_modeinfo.streamid == 0) ){
uint8_t cs[10];
::memcpy(cs, &(buf.data()[12]), 6);
decode_callsign(cs);
m_modeinfo.src = QString((char *)cs);
::memcpy(cs, &(buf.data()[6]), 6);
decode_callsign(cs);
m_modeinfo.dst = QString((char *)cs);
m_modeinfo.streamid = streamid;
m_audio->start_playback();
if((buf.data()[19] & 0x06U) == 0x04U){
m_modeinfo.type = 1;//"3200 Voice";
set_mode(true);
}
else{
m_modeinfo.type = 0;//"1600 V/D";
set_mode(false);
}
if(!m_rxtimer->isActive()){
#ifdef Q_OS_WIN
m_rxtimer->start(m_modeinfo.type ? m_rxtimerint : 32);
#else
m_rxtimer->start(m_modeinfo.type ? m_rxtimerint : m_rxtimerint*2);
#endif
}
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16);
}
else{
m_modeinfo.stream_state = STREAMING;
}
m_modeinfo.frame_number = (buf.data()[34] << 8) | (buf.data()[35] & 0xff);
m_rxwatchdog = 0;
int s = 8;
if(get_mode()){
s = 16;
}
for(int i = 0; i < s; ++i){
m_rxcodecq.append((uint8_t )buf.data()[36+i]);
}
if(m_modeinfo.frame_number & 0x8000){ // EOT
qDebug() << "M17 stream ended";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_END;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
else{
emit update(m_modeinfo);
}
}
//emit update(m_modeinfo);
}
void M17Codec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
QByteArray out;
uint8_t cs[10];
memset(cs, ' ', 9);
memcpy(cs, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size());
cs[8] = 'D';
cs[9] = 0x00;
M17Codec::encode_callsign(cs);
out.append('C');
out.append('O');
out.append('N');
out.append('N');
out.append((char *)cs, 6);
out.append(m_module);
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "CONN: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void M17Codec::send_ping()
{
QByteArray out;
uint8_t cs[10];
memset(cs, ' ', 9);
memcpy(cs, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size());
cs[8] = 'D';
cs[9] = 0x00;
encode_callsign(cs);
out.append('P');
out.append('O');
out.append('N');
out.append('G');
out.append((char *)cs, 6);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "PING: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void M17Codec::send_disconnect()
{
qDebug() << "send_disconnect()";
QByteArray out;
uint8_t cs[10];
memset(cs, ' ', 9);
memcpy(cs, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size());
cs[8] = 'D';
cs[9] = 0x00;
encode_callsign(cs);
out.append('D');
out.append('I');
out.append('S');
out.append('C');
out.append((char *)cs, 6);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void M17Codec::toggle_tx(bool tx)
{
qDebug() << "M17Codec::toggle_tx(bool tx) == " << tx;
tx ? start_tx() : stop_tx();
}
void M17Codec::start_tx()
{
m_c2->codec2_set_mode(m_txrate);
Codec::start_tx();
}
void M17Codec::transmit()
{
QByteArray txframe;
static uint16_t txstreamid = 0;
static uint16_t tx_cnt = 0;
int16_t pcm[320];
uint8_t c2[16];
#ifdef USE_FLITE
static uint16_t ttscnt = 0;
if(m_ttsid > 0){
for(int i = 0; i < 320; ++i){
if(ttscnt >= tts_audio->num_samples/2){
//audiotx_cnt = 0;
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[ttscnt*2] / 2;
ttscnt++;
}
}
m_c2->codec2_encode(c2, pcm);
if(get_mode()){
m_c2->codec2_encode(c2+8, pcm+160);
}
}
#endif
if(m_ttsid == 0){
if(m_audio->read(pcm, 320)){
m_c2->codec2_encode(c2, pcm);
if(get_mode()){
m_c2->codec2_encode(c2+8, pcm+160);
}
}
else{
return;
}
}
txframe.clear();
emit update_output_level(m_audio->level());
int r = get_mode() ? 0x05 : 0x07;
if(m_tx){
if(txstreamid == 0){
txstreamid = static_cast<uint16_t>((::rand() & 0xFFFF));
//std::cerr << "txstreamid == " << txstreamid << std::endl;
}
uint8_t src[10];
uint8_t dst[10];
memset(dst, ' ', 9);
memcpy(dst, m_hostname.toLocal8Bit(), m_hostname.size());
dst[8] = m_module;
dst[9] = 0x00;
encode_callsign(dst);
memset(src, ' ', 9);
memcpy(src, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size());
src[8] = 'D';
src[9] = 0x00;
encode_callsign(src);
txframe.append('M');
txframe.append('1');
txframe.append('7');
txframe.append(' ');
txframe.append(txstreamid >> 8);
txframe.append(txstreamid & 0xff);
txframe.append((char *)dst, 6);
txframe.append((char *)src, 6);
txframe.append('\x00');
txframe.append(r); // Frame type voice only
txframe.append(14, 0x00); //Blank nonce
txframe.append((char)(tx_cnt >> 8));
txframe.append((char)tx_cnt & 0xff);
txframe.append((char *)c2, 16);
txframe.append(2, 0x00);
//QString ss = QString("%1").arg(txstreamid, 4, 16, QChar('0'));
//QString n = QString("TX %1").arg(tx_cnt, 4, 16, QChar('0'));
m_udp->writeDatagram(txframe, m_address, m_modeinfo.port);
++tx_cnt;
m_modeinfo.src = m_modeinfo.callsign;
m_modeinfo.dst = m_hostname;
m_modeinfo.type = get_mode();// ? "3200 Voice" : "1600 V/D";
m_modeinfo.frame_number = tx_cnt;
m_modeinfo.streamid = txstreamid;
emit update(m_modeinfo);
fprintf(stderr, "SEND:%d: ", txframe.size());
for(int i = 0; i < txframe.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txframe.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
}
else{
const uint8_t quiet3200[] = { 0x00, 0x01, 0x43, 0x09, 0xe4, 0x9c, 0x08, 0x21 };
const uint8_t quiet1600[] = { 0x01, 0x00, 0x04, 0x00, 0x25, 0x75, 0xdd, 0xf2 };
const uint8_t *quiet = (get_mode()) ? quiet3200 : quiet1600;
uint8_t src[10];
uint8_t dst[10];
memset(dst, ' ', 9);
memcpy(dst, m_hostname.toLocal8Bit(), m_hostname.size());
dst[8] = m_module;
dst[9] = 0x00;
encode_callsign(dst);
memset(src, ' ', 9);
memcpy(src, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size());
src[8] = 'D';
src[9] = 0x00;
M17Codec::encode_callsign(src);
tx_cnt |= 0x8000u;
txframe.append('M');
txframe.append('1');
txframe.append('7');
txframe.append(' ');
txframe.append(txstreamid >> 8);
txframe.append(txstreamid & 0xff);
txframe.append((char *)dst, 6);
txframe.append((char *)src, 6);
txframe.append('\x00');
txframe.append(r); // Frame type voice only
txframe.append(14, 0x00); //Blank nonce
txframe.append((char)(tx_cnt >> 8));
txframe.append((char)tx_cnt & 0xff);
txframe.append((char *)quiet, 8);
txframe.append((char *)quiet, 8);
txframe.append(2, 0x00);
//QString n = QString("%1").arg(tx_cnt, 4, 16, QChar('0'));
m_udp->writeDatagram(txframe, m_address, m_modeinfo.port);
txstreamid = 0;
tx_cnt = 0;
#ifdef USE_FLITE
ttscnt = 0;
#endif
m_txtimer->stop();
if(m_ttsid == 0){
m_audio->stop_capture();
}
m_modeinfo.src = m_modeinfo.callsign;
m_modeinfo.dst = m_hostname;
m_modeinfo.type = get_mode();// ? "3200 Voice" : "1600 V/D";
m_modeinfo.frame_number = tx_cnt;
m_modeinfo.streamid = txstreamid;
emit update(m_modeinfo);
fprintf(stderr, "LAST:%d: ", txframe.size());
for(int i = 0; i < txframe.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txframe.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
}
}
void M17Codec::process_rx_data()
{
int16_t pcm[320];
uint8_t codec2[8];
if(m_rxwatchdog++ > 50){
qDebug() << "RX stream timeout ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_LOST;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
if((!m_tx) && (m_rxcodecq.size() > 7) ){
for(int i = 0; i < 8; ++i){
codec2[i] = m_rxcodecq.dequeue();
}
m_c2->codec2_decode(pcm, codec2);
int s = get_mode() ? 160 : 320;
m_audio->write(pcm, s);
emit update_output_level(m_audio->level());
}
else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){
m_rxtimer->stop();
m_audio->stop_playback();
m_rxwatchdog = 0;
m_modeinfo.streamid = 0;
m_rxcodecq.clear();
qDebug() << "M17 playback stopped";
return;
}
}

@ -0,0 +1,52 @@
/*
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/>.
*/
#ifndef M17CODEC_H
#define M17CODEC_H
#include <string>
#include "codec.h"
#include "codec2/codec2.h"
class M17Codec : public Codec
{
Q_OBJECT
public:
M17Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout);
~M17Codec();
static void encode_callsign(uint8_t *);
static void decode_callsign(uint8_t *);
void decode_c2(int16_t *, uint8_t *);
void encode_c2(int16_t *, uint8_t *);
void set_mode(bool m){ m_c2->codec2_set_mode(m);}
bool get_mode(){ return m_c2->codec2_get_mode(); }
CCodec2 *m_c2;
private slots:
void process_udp();
void send_ping();
void send_disconnect();
void toggle_tx(bool);
void start_tx();
void transmit();
void hostname_lookup(QHostInfo i);
void rate_changed(int r) { m_txrate = r; }
void process_rx_data();
private:
int m_txrate;
};
#endif // M17CODEC_H

@ -0,0 +1,44 @@
/*
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 <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QIcon>
#include <fcntl.h>
#include <sys/mman.h>
#include "droidstar.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQuickStyle::setStyle("Fusion");
app.setWindowIcon(QIcon(":/images/droidstar.png"));
qmlRegisterType<DroidStar>("org.dudetronics.droidstar", 1, 0, "DroidStar");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QGuiApplication::quit);
return app.exec();
}

@ -0,0 +1,481 @@
/*
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/>.
*/
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.3
import QtQuick.Dialogs 1.3
import org.dudetronics.droidstar 1.0
ApplicationWindow {
// @disable-check M16
visible: true
// @disable-check M16
width: 320
// @disable-check M16
height: 480
// @disable-check M16
// @disable-check M16
title: qsTr("DroidStar")
palette.window: "#252424"
palette.button: "#252424"
palette.buttonText: "white"
palette.base: "black"
palette.text: "white"
palette.windowText: "white"
palette.highlight: "steelblue"
MessageDialog {
id: idcheckDialog
title: "Invalid credentials"
text: "A valid callsign and DMR ID are required to use Dudestar on any mode, and they must match. If you have entered a valid DMR ID that matches the entered callsign, and you are still seeing this message, then you either have to click update ID files button or wait until your DMR ID is added to the ID file and try again."
}
MessageDialog {
id: errorDialog
title: "Error"
}
MessageDialog {
id: updateDialog
title: "Updating..."
text: "Check log tab for details"
}
MessageDialog {
id: disclaimerDialog
title: "D-Star WARNING"
text: "DroidStar should only be used to monitor D-Star reflectors.\nDO NOT USE DROIDSTAR FOR GENERAL DSTAR TX.\nListening to D-Star reflectors is fine.\nD-Star vocoder quality is not very good, so transmitting should\nonly be done for experimentation and development purposes.\n\nYOU HAVE BEEN WARNED!"
}
TabBar {
id: bar
width: parent.width
currentIndex: swiper.currentIndex
background: Rectangle {
color: "steelblue"
}
TabButton {
id: mainButton
padding: 10
background: Rectangle {
color: bar.currentIndex === 0 ? "steelblue" : "#353535"
}
text: qsTr("Main")
}
TabButton {
id: settingsButton
padding: 10
background: Rectangle {
color: bar.currentIndex === 1 ? "steelblue" : "#353535"
}
text: qsTr("Settings")
}
TabButton {
id: logButton
padding: 10
background: Rectangle {
color: bar.currentIndex === 2 ? "steelblue" : "#353535"
}
text: qsTr("Log")
}
TabButton {
id: hostsButton
padding: 10
background: Rectangle {
color: bar.currentIndex === 3 ? "steelblue" : "#353535"
}
text: qsTr("Hosts")
}
TabButton {
id: aboutButton
padding: 10
background: Rectangle {
color: bar.currentIndex === 4 ? "steelblue" : "#353535"
}
text: qsTr("About")
}
}
SwipeView {
id: swiper
width: parent.width
height: parent.height - 50
x: 0
y: 50
currentIndex: bar.currentIndex
interactive: false
MainTab{
id: mainTab
}
SettingsTab{
id: settingsTab
}
LogTab{
id: logTab
}
HostsTab{
id: hostsTab
}
AboutTab{}
}
DroidStar {
id: droidstar
}
Connections {
target: Qt.application
function onStateChanged() {
//console.debug("applicationStateChanged: " + Qt.application.state)
}
}
Connections {
target: droidstar
Component.onCompleted: {
mainTab.comboMode.loaded = true;
droidstar.process_settings();
settingsTab.comboVocoder.model = droidstar.get_vocoders();
settingsTab.comboModem.model = droidstar.get_modems();
settingsTab.comboPlayback.model = droidstar.get_playbacks();
settingsTab.comboCapture.model = droidstar.get_captures();
}
function onSwtx_state(s){
mainTab.swtxBox.checked = s;
mainTab.swtxBox.enabled = !s;
}
function onSwrx_state(s){
mainTab.swrxBox.checked = s;
mainTab.swrxBox.enabled = !s;
}
function onMycall_changed(s){
settingsTab.mycallEdit.text = s;
}
function onUrcall_changed(s){
settingsTab.urcallEdit.text = s;
}
function onRptr1_changed(s){
settingsTab.rptr1Edit.text = s;
}
function onRptr2_changed(s){
settingsTab.rptr2Edit.text = s;
}
function onMode_changed() {
//console.log("onMode_changed ", mainTab.comboMode.find(droidstar.get_mode()), ":", droidstar.get_mode(), ":", droidstar.get_ref_host(), ":", droidstar.get_module());
mainTab.label1.text = droidstar.get_label1();
mainTab.label2.text = droidstar.get_label2();
mainTab.label3.text = droidstar.get_label3();
mainTab.label4.text = droidstar.get_label4();
mainTab.label5.text = droidstar.get_label5();
mainTab.label6.text = droidstar.get_label6();
droidstar.set_modelchange(true);
mainTab.comboHost.model = droidstar.get_hosts();
droidstar.set_modelchange(false);
mainTab.comboMode.currentIndex = mainTab.comboMode.find(droidstar.get_mode());
if(droidstar.get_mode() === "REF"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ref_host());
mainTab.comboModule.visible = true;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = true;
settingsTab.sliderMicGain.value = 0.0;
}
if(droidstar.get_mode() === "DCS"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dcs_host());
mainTab.comboModule.visible = true;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = true;
settingsTab.sliderMicGain.value = 0.0;
}
if(droidstar.get_mode() === "XRF"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_xrf_host());
mainTab.comboModule.visible = true;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = true;
settingsTab.sliderMicGain.value = 0.0;
}
if(droidstar.get_mode() === "YSF"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ysf_host());
mainTab.comboModule.visible = false;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = false;
settingsTab.sliderMicGain.value = 0.2;
}
if(droidstar.get_mode() === "FCS"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_fcs_host());
mainTab.comboModule.visible = false;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = false;
settingsTab.sliderMicGain.value = 0.2;
}
if(droidstar.get_mode() === "DMR"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dmr_host());
mainTab.comboModule.visible = false;
mainTab.element3.visible = true;
mainTab.dmrtgidEdit.visible = true;
mainTab.privateBox.visible = true;
mainTab.element4.visible = false;
settingsTab.sliderMicGain.value = 0.3;
}
if(droidstar.get_mode() === "P25"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_p25_host());
mainTab.comboModule.visible = false;
mainTab.element3.visible = true;
mainTab.dmrtgidEdit.visible = true;
mainTab.privateBox.visible = false;
mainTab.element4.visible = false;
settingsTab.sliderMicGain.value = 0.3;
}
if(droidstar.get_mode() === "NXDN"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_nxdn_host());
mainTab.comboModule.visible = false;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = false;
settingsTab.sliderMicGain.value = 0.3;
}
if(droidstar.get_mode() === "M17"){
mainTab.comboHost.visible = true;
mainTab.element1.text = "Host";
mainTab.editIAXDTMF.visible = false;
mainTab.dtmfsendbutton.visible = false;
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_m17_host());
mainTab.comboModule.currentIndex = mainTab.comboModule.find(droidstar.get_module());
mainTab.comboModule.visible = true;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = true;
settingsTab.sliderMicGain.value = 0.5;
}
if(droidstar.get_mode() === "IAX"){
mainTab.comboHost.visible = false;
mainTab.element1.text = "DTMF *";
mainTab.editIAXDTMF.visible = true;
mainTab.dtmfsendbutton.visible = true;
mainTab.comboModule.visible = false;
mainTab.element3.visible = false;
mainTab.dmrtgidEdit.visible = false;
mainTab.privateBox.visible = false;
mainTab.element4.visible = false;
settingsTab.sliderMicGain.value = 0.5;
}
}
function onUpdate_data() {
mainTab.data1.text = droidstar.get_data1();
mainTab.data2.text = droidstar.get_data2();
mainTab.data3.text = droidstar.get_data3();
mainTab.data4.text = droidstar.get_data4();
mainTab.data5.text = droidstar.get_data5();
mainTab.data6.text = droidstar.get_data6();
mainTab.status.text = droidstar.get_statustxt();
++mainTab.uitimer.rxcnt;
}
function onUpdate_settings() {
//console.log("update_settings comboHost == ", mainTab.comboHost.find(droidstar.get_host()));
//console.log("update_settings comboModule == ", mainTab.comboModule.find(droidstar.get_module()));
settingsTab.ipv6.checked = droidstar.get_ipv6();
settingsTab.xrf2ref.checked = droidstar.get_xrf2ref();
settingsTab.toggleTX.checked = droidstar.get_toggletx();
if(droidstar.get_mode() === "REF"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ref_host());
}
if(droidstar.get_mode() === "DCS"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dcs_host());
}
if(droidstar.get_mode() === "XRF"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_xrf_host());
}
if(droidstar.get_mode() === "YSF"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ysf_host());
}
if(droidstar.get_mode() === "FCS"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_fcs_host());
}
if(droidstar.get_mode() === "DMR"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dmr_host());
}
if(droidstar.get_mode() === "P25"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_p25_host());
}
if(droidstar.get_mode() === "NXDN"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_nxdn_host());
}
if(droidstar.get_mode() === "M17"){
mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_m17_host());
}
mainTab.comboModule.currentIndex = mainTab.comboModule.find(droidstar.get_module());
settingsTab.callsignEdit.text = droidstar.get_callsign();
settingsTab.dmridEdit.text = droidstar.get_dmrid();
settingsTab.comboEssid.currentIndex = settingsTab.comboEssid.find(droidstar.get_essid());
settingsTab.bmpwEdit.text = droidstar.get_bm_password();
settingsTab.tgifpwEdit.text = droidstar.get_tgif_password();
settingsTab.latEdit.text = droidstar.get_latitude();
settingsTab.lonEdit.text = droidstar.get_longitude();
settingsTab.locEdit.text = droidstar.get_location();
settingsTab.descEdit.text = droidstar.get_description();
settingsTab.urlEdit.text = droidstar.get_url();
settingsTab.swidEdit.text = droidstar.get_swid();
settingsTab.pkgidEdit.text = droidstar.get_pkgid();
settingsTab.dmroptsEdit.text = droidstar.get_dmr_options();
mainTab.dmrtgidEdit.text = droidstar.get_dmrtgid();
settingsTab.iaxuserEdit.text = droidstar.get_iax_user();
settingsTab.iaxpassEdit.text = droidstar.get_iax_pass();
settingsTab.iaxnodeEdit.text = droidstar.get_iax_node();
settingsTab.iaxhostEdit.text = droidstar.get_iax_host();
settingsTab.iaxportEdit.text = droidstar.get_iax_port();
settingsTab.mycallEdit.text = droidstar.get_mycall();
settingsTab.urcallEdit.text = droidstar.get_urcall();
settingsTab.rptr1Edit.text = droidstar.get_rptr1();
settingsTab.rptr2Edit.text = droidstar.get_rptr2();
settingsTab.txtimerEdit.text = droidstar.get_txtimeout();
settingsTab.modemRXFreqEdit.text = droidstar.get_modemRxFreq();
settingsTab.modemTXFreqEdit.text = droidstar.get_modemTxFreq();
settingsTab.modemRXOffsetEdit.text = droidstar.get_modemRxOffset();
settingsTab.modemTXOffsetEdit.text = droidstar.get_modemTxOffset();
settingsTab.modemRXDCOffsetEdit.text = droidstar.get_modemRxDCOffset();
settingsTab.modemTXDCOffsetEdit.text = droidstar.get_modemTxDCOffset();
settingsTab.modemRXLevelEdit.text = droidstar.get_modemRxLevel();
settingsTab.modemTXLevelEdit.text = droidstar.get_modemTxLevel();
settingsTab.modemRFLevelEdit.text = droidstar.get_modemRFLevel();
settingsTab.modemTXDelayEdit.text = droidstar.get_modemTxDelay();
settingsTab.modemCWIdTXLevelEdit.text = droidstar.get_modemCWIdTxLevel();
settingsTab.modemDStarTXLevelEdit.text = droidstar.get_modemDstarTxLevel();
settingsTab.modemDMRTXLevelEdit.text = droidstar.get_modemDMRTxLevel();
settingsTab.modemYSFTXLevelEdit.text = droidstar.get_modemYSFTxLevel();
settingsTab.modemP25TXLevelEdit.text = droidstar.get_modemP25TxLevel()
settingsTab.modemNXDNTXLevelEdit.text = droidstar.get_modemNXDNTxLevel();
hostsTab.hostsTextEdit.text = droidstar.get_local_hosts();
}
function onUpdate_log(s) {
logTab.logText.append(s);
}
function onConnect_status_changed(c) {
if(c === 0){
if(mainTab.buttonTX.tx){
mainTab.buttonTX.tx = false;
droidstar.tx_clicked(false);
mainTab.txtimer.running = false;
mainTab.btntxt.color = "black";
mainTab.btntxt.text = "TX";
}
mainTab.connectbutton.text = "Connect";
mainTab.comboMode.enabled = true;
mainTab.comboHost.enabled = true;
mainTab.comboModule.enabled = true;
mainTab.buttonTX.enabled = false;
mainTab.btntxt.color = "steelblue";
mainTab.data1.text = "";
mainTab.data2.text = "";
mainTab.data3.text = "";
mainTab.data4.text = "";
mainTab.data5.text = "";
mainTab.data6.text = "";
mainTab.status.text = "Not connected";
}
if(c === 1){
mainTab.connectbutton.text = "Connecting";
mainTab.comboMode.enabled = false;
mainTab.comboHost.enabled = false;
if(mainTab.comboMode.currentText != "REF"){
mainTab.comboModule.enabled = false;
}
}
if(c === 2){
mainTab.connectbutton.text = "Disconnect";
mainTab.comboMode.enabled = false;
mainTab.comboHost.enabled = false;
if(mainTab.comboMode.currentText != "REF"){
mainTab.comboModule.enabled = false;
}
if( (mainTab.comboMode.currentText == "REF") ||
(mainTab.comboMode.currentText == "DCS") ||
(mainTab.comboMode.currentText == "XRF"))
{
disclaimerDialog.open();
}
if(mainTab.comboMode.currentText === "YSF"){
settingsTab.m171600.checked = true;
}
if(mainTab.comboMode.currentText === "FCS"){
settingsTab.m171600.checked = true;
}
if(mainTab.comboMode.currentText === "M17"){
settingsTab.m173200.checked = true;
}
mainTab.buttonTX.enabled = true;
mainTab.btntxt.color = "black";
mainTab.agcBox.checked = true;
}
if(c === 3){
connectDialog.open();
}
if(c === 4){
idcheckDialog.open();
onConnect_status_changed(0);
}
if(c === 5){
errorDialog.text = droidstar.get_error_text();
errorDialog.open();
droidstar.onConnect_status_changed(0);
}
}
}
}

@ -0,0 +1,31 @@
/*
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/>.
*/
#ifndef __MicPermission_h_
#define __MicPermission_h_
#include <QObject>
#include <QString>
class MicPermission : public QObject
{
Q_OBJECT
public:
static int check_permission();
};
#endif

@ -0,0 +1,31 @@
/*
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 "micpermission.h"
//#import <Foundation/NSUserNotification.h>
//#import <Foundation/NSString.h>
#import <AVFoundation/AVCaptureDevice.h>
int MicPermission::check_permission()
{
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
if(status != AVAuthorizationStatusAuthorized){
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
}];
}
return status;
}

@ -0,0 +1,714 @@
/*
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 "nxdncodec.h"
#include <cstring>
#define DEBUG
const int dvsi_interleave[49] = {
0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 41, 43, 45, 47,
1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 42, 44, 46, 48,
2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38
};
const uint8_t NXDN_LICH_RFCT_RDCH = 2U;
const uint8_t NXDN_LICH_USC_SACCH_NS = 0U;
const uint8_t NXDN_LICH_USC_SACCH_SS = 2U;
const uint8_t NXDN_LICH_STEAL_FACCH = 0U;
const uint8_t NXDN_LICH_STEAL_NONE = 3U;
const uint8_t NXDN_LICH_DIRECTION_INBOUND = 0U;
const uint8_t NXDN_MESSAGE_TYPE_VCALL = 1U;
const uint8_t NXDN_MESSAGE_TYPE_TX_REL = 8U;
const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U };
#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
NXDNCodec::NXDNCodec(QString callsign, uint16_t nxdnid, uint32_t gwid, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) :
Codec(callsign, 0, NULL, host, port, ipv6, vocoder, modem, audioin, audioout),
m_nxdnid(nxdnid)
{
m_txcnt = 0;
m_txtimerint = 19;
m_modeinfo.gwid = gwid;
}
NXDNCodec::~NXDNCodec()
{
}
void NXDNCodec::process_udp()
{
QByteArray buf;
QHostAddress sender;
quint16 senderPort;
uint8_t ambe[7];
buf.resize(m_udp->pendingDatagramSize());
m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
fprintf(stderr, "RECV: ");
for(int i = 0; i < buf.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
if(buf.size() == 17){
if(m_modeinfo.status == CONNECTING){
m_modeinfo.status = CONNECTED_RW;
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
m_txtimer = new QTimer();
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_ping_timer = new QTimer();
connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping()));
//m_mbeenc->set_gain_adjust(2.5);
m_modeinfo.vocoder_loaded = load_vocoder_plugin();
if(m_vocoder != ""){
m_hwrx = true;
m_hwtx = true;
m_ambedev = new SerialAMBE("NXDN");
m_ambedev->connect_to_serial(m_vocoder);
connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe()));
}
else{
m_hwrx = false;
m_hwtx = false;
}
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
m_ping_timer->start(1000);
}
if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){
m_modeinfo.stream_state = STREAM_IDLE;
}
m_modeinfo.count++;
}
if(buf.size() == 43){
m_modeinfo.srcid = (uint16_t)((buf.data()[5] << 8) & 0xff00) | (buf.data()[6] & 0xff);
m_modeinfo.dstid = (uint16_t)((buf.data()[7] << 8) & 0xff00) | (buf.data()[8] & 0xff);
if(get_lich_fct(buf.data()[10U]) == NXDN_LICH_USC_SACCH_NS){
if((buf.data()[9U] & 0x08) == 0x08){
qDebug() << "Received EOT";
m_modeinfo.frame_number = 0;
m_modeinfo.stream_state = STREAM_END;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
m_modeinfo.streamid = 0;
}
else{
if(!m_rxtimer->isActive()){
m_audio->start_playback();
m_rxtimer->start(m_rxtimerint);
}
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
qDebug() << "New NXDN stream from " << m_modeinfo.srcid << " to " << m_modeinfo.dstid;
}
}
else if(!m_tx && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_IDLE) )){
if(!m_rxtimer->isActive()){
m_audio->start_playback();
m_rxtimer->start(m_rxtimerint);
}
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
qDebug() << "New NXDN stream in progress from " << m_modeinfo.srcid << " to " << m_modeinfo.dstid;
}
else{
m_modeinfo.stream_state = STREAMING;
m_modeinfo.frame_number++;
}
m_rxwatchdog = 0;
memcpy(ambe, buf.data() + 15, 7);
if(m_hwrx){
interleave(ambe);
}
for(int i = 0; i < 7; ++i){
m_rxcodecq.append(ambe[i]);
}
char t[7];
char *d = &(buf.data()[21]);
for(int i = 0; i < 6; ++i){
t[i] = d[i] << 1;
t[i] |= (1 & (d[i+1] >> 7));
}
t[6] = d[6] << 1;
memcpy(ambe, t, 7);
if(m_hwrx){
interleave(ambe);
}
for(int i = 0; i < 7; ++i){
m_rxcodecq.append(ambe[i]);
}
memcpy(ambe, buf.data() + 29, 7);
if(m_hwrx){
interleave(ambe);
}
for(int i = 0; i < 7; ++i){
m_rxcodecq.append(ambe[i]);
}
d = &(buf.data()[35]);
for(int i = 0; i < 6; ++i){
t[i] = d[i] << 1;
t[i] |= (1 & (d[i+1] >> 7));
}
t[6] = d[6] << 1;
memcpy(ambe, t, 7);
if(m_hwrx){
interleave(ambe);
}
for(int i = 0; i < 7; ++i){
m_rxcodecq.append(ambe[i]);
}
}
emit update(m_modeinfo);
}
void NXDNCodec::interleave(uint8_t *ambe)
{
char ambe_data[49];
char dvsi_data[7];
memset(dvsi_data, 0, 7);
for(int i = 0; i < 6; ++i){
for(int j = 0; j < 8; j++){
ambe_data[j+(8*i)] = (1 & (ambe[i] >> (7 - j)));
}
}
ambe_data[48] = (1 & (ambe[6] >> 7));
for(int i = 0, j; i < 49; ++i){
j = dvsi_interleave[i];
dvsi_data[j/8] += (ambe_data[i])<<(7-(j%8));
}
memcpy(ambe, dvsi_data, 7);
}
void NXDNCodec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
QByteArray out;
out.append('N');
out.append('X');
out.append('D');
out.append('N');
out.append('P');
out.append(m_modeinfo.callsign.toUtf8());
out.append(10 - m_modeinfo.callsign.size(), ' ');
out.append((m_modeinfo.gwid >> 8) & 0xff);
out.append((m_modeinfo.gwid >> 0) & 0xff);
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "CONN: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void NXDNCodec::send_ping()
{
QByteArray out;
out.append('N');
out.append('X');
out.append('D');
out.append('N');
out.append('P');
out.append(m_modeinfo.callsign.toUtf8());
out.append(10 - m_modeinfo.callsign.size(), ' ');
out.append((m_modeinfo.gwid >> 8) & 0xff);
out.append((m_modeinfo.gwid >> 0) & 0xff);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "PING: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void NXDNCodec::send_disconnect()
{
QByteArray out;
out.append('N');
out.append('X');
out.append('D');
out.append('N');
out.append('U');
out.append(m_modeinfo.callsign.toUtf8());
out.append(10 - m_modeinfo.callsign.size(), ' ');
out.append((m_modeinfo.gwid >> 8) & 0xff);
out.append((m_modeinfo.gwid >> 0) & 0xff);
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void NXDNCodec::transmit()
{
uint8_t ambe_frame[49];
uint8_t ambe[7];
int16_t pcm[160];
memset(ambe, 0, 7);
#ifdef USE_FLITE
if(m_ttsid > 0){
for(int i = 0; i < 160; ++i){
if(m_ttscnt >= tts_audio->num_samples/2){
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[m_ttscnt*2] / 2;
m_ttscnt++;
}
}
}
#endif
if(m_ttsid == 0){
if(m_audio->read(pcm, 160)){
}
else{
return;
}
}
if(m_hwtx){
m_ambedev->encode(pcm);
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->encode_2450(pcm, ambe_frame);
}
for(int i = 0; i < 7; ++i){
for(int j = 0; j < 8; ++j){
ambe[i] |= (ambe_frame[(i*8)+j] << (7-j));
}
ambe[6] &= 0x80;
m_txcodecq.append(ambe[i]);
}
}
if(m_tx && (m_txcodecq.size() >= 28)){
for(int i = 0; i < 28; ++i){
m_ambe[i] = m_txcodecq.dequeue();
}
send_frame();
}
else if(m_tx == false){
send_frame();
}
}
void NXDNCodec::send_frame()
{
QByteArray txdata;
unsigned char *temp_nxdn;
if(m_tx){
m_modeinfo.stream_state = TRANSMITTING;
temp_nxdn = get_frame();
txdata.append((char *)temp_nxdn, 43);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
fprintf(stderr, "SEND:%d: ", txdata.size());
for(int i = 0; i < txdata.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
}
else{
fprintf(stderr, "NXDN TX stopped\n");
m_txtimer->stop();
temp_nxdn = get_eot();
m_ttscnt = 0;
txdata.append((char *)temp_nxdn, 43);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
m_modeinfo.stream_state = STREAM_IDLE;
}
m_modeinfo.srcid = m_nxdnid;
m_modeinfo.frame_number = m_txcnt;
m_modeinfo.dstid = m_modeinfo.gwid;
emit update_output_level(m_audio->level());
emit update(m_modeinfo);
}
uint8_t * NXDNCodec::get_frame()
{
memcpy(m_nxdnframe, "NXDND", 5);
m_nxdnframe[5U] = (m_nxdnid >> 8) & 0xFFU;
m_nxdnframe[6U] = (m_nxdnid >> 0) & 0xFFU;
m_nxdnframe[7U] = (m_modeinfo.gwid >> 8) & 0xFFU;
m_nxdnframe[8U] = (m_modeinfo.gwid >> 0) & 0xFFU;
m_nxdnframe[9U] = 0x01U;
if(!m_txcnt || m_eot){
encode_header();
}
else{
encode_data();
}
if (m_nxdnframe[10U] == 0x81U || m_nxdnframe[10U] == 0x83U) {
m_nxdnframe[9U] |= m_nxdnframe[15U] == 0x01U ? 0x04U : 0x00U;
m_nxdnframe[9U] |= m_nxdnframe[15U] == 0x08U ? 0x08U : 0x00U;
}
else if ((m_nxdnframe[10U] & 0xF0U) == 0x90U) {
m_nxdnframe[9U] |= 0x02U;
if (m_nxdnframe[10U] == 0x90U || m_nxdnframe[10U] == 0x92U || m_nxdnframe[10U] == 0x9CU || m_nxdnframe[10U] == 0x9EU) {
m_nxdnframe[9U] |= m_nxdnframe[12U] == 0x09U ? 0x04U : 0x00U;
m_nxdnframe[9U] |= m_nxdnframe[12U] == 0x08U ? 0x08U : 0x00U;
}
}
if(m_eot){
m_txcnt = 0;
m_eot = false;
}
else{
++m_txcnt;
}
return m_nxdnframe;
}
void NXDNCodec::encode_header()
{
const uint8_t idle[3U] = {0x10, 0x00, 0x00};
m_lich = 0;
memset(m_sacch, 0, 5U);
memset(m_layer3, 0, 22U);
set_lich_rfct(NXDN_LICH_RFCT_RDCH);
set_lich_fct(NXDN_LICH_USC_SACCH_NS);
set_lich_option(NXDN_LICH_STEAL_FACCH);
set_lich_dir(NXDN_LICH_DIRECTION_INBOUND);
m_nxdnframe[10U] = get_lich();
set_sacch_ran(0x01);
set_sacch_struct(0); //Single
set_sacch_data(idle);
get_sacch(&m_nxdnframe[11U]);
if(m_eot){
set_layer3_msgtype(NXDN_MESSAGE_TYPE_TX_REL);
}
else{
set_layer3_msgtype(NXDN_MESSAGE_TYPE_VCALL);
}
set_layer3_srcid(m_nxdnid);
set_layer3_dstid(m_modeinfo.gwid);
set_layer3_grp(true);
set_layer3_blks(0U);
memcpy(&m_nxdnframe[15U], m_layer3, 14U);
memcpy(&m_nxdnframe[29U], m_layer3, 14U);
}
void NXDNCodec::encode_data()
{
uint8_t msg[3U];
m_lich = 0;
memset(m_sacch, 0, 5U);
memset(m_layer3, 0, 22U);
set_lich_rfct(NXDN_LICH_RFCT_RDCH);
set_lich_fct(NXDN_LICH_USC_SACCH_SS);
set_lich_option(NXDN_LICH_STEAL_NONE);
set_lich_dir(NXDN_LICH_DIRECTION_INBOUND);
m_nxdnframe[10U] = get_lich();
set_sacch_ran(0x01);
set_layer3_msgtype(NXDN_MESSAGE_TYPE_VCALL);
set_layer3_srcid(m_nxdnid);
set_layer3_dstid(m_modeinfo.gwid);
set_layer3_grp(true);
set_layer3_blks(0U);
switch(m_txcnt % 4){
case 0:
set_sacch_struct(3);
layer3_encode(msg, 18U, 0U);
set_sacch_data(msg);
break;
case 1:
set_sacch_struct(2);
layer3_encode(msg, 18U, 18U);
set_sacch_data(msg);
break;
case 2:
set_sacch_struct(1);
layer3_encode(msg, 18U, 36U);
set_sacch_data(msg);
break;
case 3:
set_sacch_struct(0);
layer3_encode(msg, 18U, 54U);
set_sacch_data(msg);
break;
}
get_sacch(&m_nxdnframe[11U]);
if(m_hwtx){
for(int i = 0; i < 4; ++i){
deinterleave_ambe(&m_ambe[7*i]);
}
}
memcpy(&m_nxdnframe[15], m_ambe, 7);
for(int i = 0; i < 7; ++i){
m_nxdnframe[21+i] |= (m_ambe[7+i] >> 1);
m_nxdnframe[22+i] = (m_ambe[7+i] & 1) << 7;
}
m_nxdnframe[28] |= (m_ambe[13] >> 2);
memcpy(&m_nxdnframe[29], &m_ambe[14], 7);
for(int i = 0; i < 7; ++i){
m_nxdnframe[35+i] |= (m_ambe[21+i] >> 1);
m_nxdnframe[36+i] = (m_ambe[21+i] & 1) << 7;
}
m_nxdnframe[41] |= (m_ambe[27] >> 2);
}
void NXDNCodec::deinterleave_ambe(uint8_t *d)
{
uint8_t dvsi_data[49];
uint8_t ambe_data[7];
memset(ambe_data, 0, 7);
for(int i = 0; i < 6; ++i){
for(int j = 0; j < 8; j++){
dvsi_data[j+(8*i)] = (1 & (d[i] >> (7 - j)));
}
}
dvsi_data[48] = (1 & (d[6] >> 7));
for(int i = 0, j; i < 49; ++i){
j = dvsi_interleave[i];
ambe_data[i/8] += (dvsi_data[j])<<(7-(i%8));
//j = dvsi_deinterleave[i];
//ambe_data[j/8] += (dvsi_data[i])<<(7-(j%8));
}
memcpy(d, ambe_data, 7);
}
unsigned char NXDNCodec::get_lich_fct(uint8_t lich)
{
return (lich >> 4) & 0x03U;
}
void NXDNCodec::set_lich_rfct(uint8_t rfct)
{
m_lich &= 0x3FU;
m_lich |= (rfct << 6) & 0xC0U;
}
void NXDNCodec::set_lich_fct(uint8_t fct)
{
m_lich &= 0xCFU;
m_lich |= (fct << 4) & 0x30U;
}
void NXDNCodec::set_lich_option(uint8_t o)
{
m_lich &= 0xF3U;
m_lich |= (o << 2) & 0x0CU;
}
void NXDNCodec::set_lich_dir(uint8_t d)
{
m_lich &= 0xFDU;
m_lich |= (d << 1) & 0x02U;
}
uint8_t NXDNCodec::get_lich()
{
bool parity;
switch (m_lich & 0xF0U) {
case 0x80U:
case 0xB0U:
parity = true;
break;
default:
parity = false;
}
if (parity)
m_lich |= 0x01U;
else
m_lich &= 0xFEU;
return m_lich;
}
void NXDNCodec::set_sacch_ran(uint8_t ran)
{
m_sacch[0] &= 0xC0U;
m_sacch[0] |= ran;
}
void NXDNCodec::set_sacch_struct(uint8_t s)
{
m_sacch[0] &= 0x3FU;
m_sacch[0] |= (s << 6) & 0xC0U;;
}
void NXDNCodec::set_sacch_data(const uint8_t *d)
{
uint8_t offset = 8U;
for (uint8_t i = 0U; i < 18U; i++, offset++) {
bool b = READ_BIT1(d, i);
WRITE_BIT1(m_sacch, offset, b);
}
}
void NXDNCodec::get_sacch(uint8_t *d)
{
memcpy(d, m_sacch, 4U);
encode_crc6(d, 26);
}
void NXDNCodec::set_layer3_msgtype(uint8_t t)
{
m_layer3[0] &= 0xC0U;
m_layer3[0] |= t & 0x3FU;
}
void NXDNCodec::set_layer3_srcid(uint16_t src)
{
m_layer3[3U] = (src >> 8) & 0xFF;
m_layer3[4U] = (src >> 0) & 0xFF ;
}
void NXDNCodec::set_layer3_dstid(uint16_t dst)
{
m_layer3[5U] = (dst >> 8) & 0xFF;
m_layer3[6U] = (dst >> 0) & 0xFF ;
}
void NXDNCodec::set_layer3_grp(bool grp)
{
m_layer3[2U] |= grp ? 0x20U : 0x20U;
}
void NXDNCodec::set_layer3_blks(uint8_t b)
{
m_layer3[8U] &= 0xF0U;
m_layer3[8U] |= b & 0x0FU;
}
void NXDNCodec::layer3_encode(uint8_t* d, uint8_t len, uint8_t offset)
{
for (uint32_t i = 0U; i < len; i++, offset++) {
bool b = READ_BIT1(m_layer3, offset);
WRITE_BIT1(d, i, b);
}
}
void NXDNCodec::encode_crc6(uint8_t *d, uint8_t len)
{
uint8_t crc = 0x3FU;
for (unsigned int i = 0U; i < len; i++) {
bool bit1 = READ_BIT1(d, i) != 0x00U;
bool bit2 = (crc & 0x20U) == 0x20U;
crc <<= 1;
if (bit1 ^ bit2)
crc ^= 0x27U;
}
crc &= 0x3FU;
uint8_t n = len;
for (uint8_t i = 2U; i < 8U; i++, n++) {
bool b = READ_BIT1((&crc), i);
WRITE_BIT1(d, n, b);
}
}
void NXDNCodec::get_ambe()
{
uint8_t ambe[7];
if(m_ambedev->get_ambe(ambe)){
for(int i = 0; i < 7; ++i){
m_txcodecq.append(ambe[i]);
}
}
}
void NXDNCodec::process_rx_data()
{
int16_t pcm[160];
uint8_t ambe[7];
if(m_rxwatchdog++ > 25){
qDebug() << "NXDN RX stream timeout ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_LOST;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_rxcodecq.clear();
}
if((!m_tx) && (m_rxcodecq.size() > 6) ){
for(int i = 0; i < 7; ++i){
ambe[i] = m_rxcodecq.dequeue();
}
if(m_hwrx){
m_ambedev->decode(ambe);
if(m_ambedev->get_audio(pcm)){
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->decode_2450(pcm, ambe);
}
else{
memset(pcm, 0, 160 * sizeof(int16_t));
}
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){
m_rxtimer->stop();
m_audio->stop_playback();
m_rxwatchdog = 0;
m_modeinfo.streamid = 0;
m_rxcodecq.clear();
qDebug() << "YSF playback stopped";
return;
}
}

@ -0,0 +1,76 @@
/*
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/>.
*/
#ifndef NXDNCODEC_H
#define NXDNCODEC_H
//#include <inttypes.h>
#include "codec.h"
class NXDNCodec : public Codec
{
Q_OBJECT
public:
NXDNCodec(QString callsign, uint16_t nxdnid, uint32_t gwid, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout);
~NXDNCodec();
unsigned char * get_frame();
unsigned char * get_eot(){m_eot = true; return get_frame();}
void set_hwtx(bool hw){m_hwtx = hw;}
private slots:
void process_udp();
void process_rx_data();
void get_ambe();
void send_ping();
void send_disconnect();
void transmit();
void hostname_lookup(QHostInfo i);
void send_frame();
private:
uint16_t m_nxdnid;
bool m_eot;
uint8_t m_nxdnframe[55];
uint8_t m_lich;
uint8_t m_sacch[5];
uint8_t m_layer3[22];
uint8_t m_ambe[36];
uint8_t packet_size;
void encode_header();
void encode_data();
uint8_t get_lich_fct(uint8_t);
void set_lich_rfct(uint8_t);
void set_lich_fct(uint8_t);
void set_lich_option(uint8_t);
void set_lich_dir(uint8_t);
void set_sacch_ran(uint8_t);
void set_sacch_struct(uint8_t);
void set_sacch_data(const uint8_t *);
void set_layer3_msgtype(uint8_t);
void set_layer3_srcid(uint16_t);
void set_layer3_dstid(uint16_t);
void set_layer3_grp(bool);
void set_layer3_blks(uint8_t);
void layer3_encode(uint8_t*, uint8_t, uint8_t);
uint8_t get_lich();
void get_sacch(uint8_t *);
void encode_crc6(uint8_t *, uint8_t);
void deinterleave_ambe(uint8_t *);
void interleave(uint8_t *ambe);
};
#endif // NXDNCODEC_H

@ -0,0 +1,448 @@
/*
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 <iostream>
#include <cstring>
#include "p25codec.h"
#define DEBUG
const unsigned char REC62[] = {0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC63[] = {0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC64[] = {0x64U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC65[] = {0x65U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC66[] = {0x66U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC67[] = {0x67U, 0xF0U, 0x9DU, 0x6AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC68[] = {0x68U, 0x19U, 0xD4U, 0x26U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC69[] = {0x69U, 0xE0U, 0xEBU, 0x7BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6A[] = {0x6AU, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC6B[] = {0x6BU, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC6C[] = {0x6CU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6D[] = {0x6DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6E[] = {0x6EU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6F[] = {0x6FU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC70[] = {0x70U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC71[] = {0x71U, 0xACU, 0xB8U, 0xA4U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC72[] = {0x72U, 0x9BU, 0xDCU, 0x75U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC73[] = {0x73U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC80[] = {0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
#define WRITE_BIT(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
P25Codec::P25Codec(QString callsign, int dmrid, int hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout) :
Codec(callsign, 0, NULL, host, port, ipv6, NULL, modem, audioin, audioout),
m_hostname(hostname),
m_dmrid(dmrid)
{
m_p25cnt = 0;
m_txtimerint = 19;
}
P25Codec::~P25Codec()
{
}
void P25Codec::process_udp()
{
QByteArray buf;
QHostAddress sender;
quint16 senderPort;
buf.resize(m_udp->pendingDatagramSize());
m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
fprintf(stderr, "RCCV: ");
for(int i = 0; i < buf.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
if(buf.size() == 11){
if(m_modeinfo.status == CONNECTING){
m_modeinfo.status = CONNECTED_RW;
m_modeinfo.status = CONNECTED_RW;
m_txtimer = new QTimer();
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_ping_timer = new QTimer();
connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping()));
m_ping_timer->start(5000);
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
}
if((m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){
m_modeinfo.stream_state = STREAM_IDLE;
}
m_modeinfo.count++;
emit update(m_modeinfo);
}
if(buf.size() > 11){
if( (m_modeinfo.stream_state == STREAM_END) ||
(m_modeinfo.stream_state == STREAM_LOST) ||
(m_modeinfo.stream_state == STREAM_IDLE))
{
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
if(!m_tx && !m_rxtimer->isActive() ){
m_rxcodecq.clear();
m_audio->start_playback();
m_rxtimer->start(m_rxtimerint);
}
qDebug() << "New P25 stream";
}
else{
m_modeinfo.stream_state = STREAMING;
}
m_rxwatchdog = 0;
int offset = 0;
m_modeinfo.frame_number = buf.data()[0U];
switch ((uint8_t)buf.data()[0U]) {
case 0x62U:
offset = 10U;
break;
case 0x63U:
offset = 1U;
break;
case 0x64U:
offset = 5U;
break;
case 0x65U:
m_modeinfo.dstid = (uint32_t)((buf.data()[1] << 16) | ((buf.data()[2] << 8) & 0xff00) | (buf.data()[3] & 0xff));
offset = 5U;
break;
case 0x66U:
m_modeinfo.srcid = (uint32_t)((buf.data()[1] << 16) | ((buf.data()[2] << 8) & 0xff00) | (buf.data()[3] & 0xff));
//ui->rptr1->setText(QString::number((uint32_t)((buf.data()[1] << 16) | ((buf.data()[2] << 8) & 0xff00) | (buf.data()[3] & 0xff))));
offset = 5U;
break;
case 0x67U:
case 0x68U:
case 0x69U:
offset = 5U;
break;
case 0x6AU:
offset = 4U;
break;
case 0x6BU:
offset = 10U;
break;
case 0x6CU:
offset = 1U;
break;
case 0x6DU:
case 0x6EU:
case 0x6FU:
case 0x70U:
case 0x71U:
case 0x72U:
offset = 5U;
break;
case 0x73U:
offset = 4U;
break;
case 0x80U:
m_modeinfo.stream_state = STREAM_END;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
qDebug() << "P25 stream ended";
default:
break;
}
//for(int i = 0; i < 11; ++i){
//m_codecq.enqueue(buf.data()[i + offset]);
//}
for (int i = 0; i < 11; ++i){
m_rxcodecq.append(buf.data()[offset+i]);
}
emit update(m_modeinfo);
}
}
void P25Codec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
QByteArray out;
out.append(0xf0);
out.append(m_modeinfo.callsign.toUtf8());
out.append(10 - m_modeinfo.callsign.size(), ' ');
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "CONN: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void P25Codec::send_ping()
{
QByteArray out;
out.append(0xf0);
out.append(m_modeinfo.callsign.toUtf8());
out.append(10 - m_modeinfo.callsign.size(), ' ');
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "PING: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void P25Codec::send_disconnect()
{
QByteArray out;
out.append(0xf1);
out.append(m_modeinfo.callsign.toUtf8());
out.append(10 - m_modeinfo.callsign.size(), ' ');
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void P25Codec::transmit()
{
QByteArray txdata;
uint8_t imbe[11];
int16_t pcm[160];
uint8_t buffer[22];
static uint8_t p25step = 0;
#ifdef USE_FLITE
if(m_ttsid > 0){
for(int i = 0; i < 160; ++i){
if(m_ttscnt >= tts_audio->num_samples/2){
//audiotx_cnt = 0;
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[m_ttscnt*2] / 2;
m_ttscnt++;
}
}
encode_4400(pcm, imbe);
}
#endif
if(m_ttsid == 0){
if(m_audio->read(pcm, 160)){
vocoder.encode_4400(pcm, imbe);
}
else{
return;
}
}
if(m_tx){
switch (p25step) {
case 0x00U:
::memcpy(buffer, REC62, 22U);
::memcpy(buffer + 10U, imbe, 11U);
txdata.append((char *)buffer, 22U);
++p25step;
break;
case 0x01U:
::memcpy(buffer, REC63, 14U);
::memcpy(buffer + 1U, imbe, 11U);
txdata.append((char *)buffer, 14U);
++p25step;
break;
case 0x02U:
::memcpy(buffer, REC64, 17U);
::memcpy(buffer + 5U, imbe, 11U);
buffer[1U] = 0x00U;
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x03U:
::memcpy(buffer, REC65, 17U);
::memcpy(buffer + 5U, imbe, 11U);
buffer[1U] = (m_hostname >> 16) & 0xFFU;
buffer[2U] = (m_hostname >> 8) & 0xFFU;
buffer[3U] = (m_hostname >> 0) & 0xFFU;
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x04U:
::memcpy(buffer, REC66, 17U);
::memcpy(buffer + 5U, imbe, 11U);
buffer[1U] = (m_dmrid >> 16) & 0xFFU;
buffer[2U] = (m_dmrid >> 8) & 0xFFU;
buffer[3U] = (m_dmrid >> 0) & 0xFFU;
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x05U:
::memcpy(buffer, REC67, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x06U:
::memcpy(buffer, REC68, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x07U:
::memcpy(buffer, REC69, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x08U:
::memcpy(buffer, REC6A, 16U);
::memcpy(buffer + 4U, imbe, 11U);
txdata.append((char *)buffer, 16U);
++p25step;
break;
case 0x09U:
::memcpy(buffer, REC6B, 22U);
::memcpy(buffer + 10U, imbe, 11U);
txdata.append((char *)buffer, 22U);
++p25step;
break;
case 0x0AU:
::memcpy(buffer, REC6C, 14U);
::memcpy(buffer + 1U, imbe, 11U);
txdata.append((char *)buffer, 14U);
++p25step;
break;
case 0x0BU:
::memcpy(buffer, REC6D, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x0CU:
::memcpy(buffer, REC6E, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x0DU:
::memcpy(buffer, REC6F, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x0EU:
::memcpy(buffer, REC70, 17U);
::memcpy(buffer + 5U, imbe, 11U);
buffer[1U] = 0x80U;
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x0FU:
::memcpy(buffer, REC71, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x10U:
::memcpy(buffer, REC72, 17U);
::memcpy(buffer + 5U, imbe, 11U);
txdata.append((char *)buffer, 17U);
++p25step;
break;
case 0x11U:
::memcpy(buffer, REC73, 16U);
::memcpy(buffer + 4U, imbe, 11U);
txdata.append((char *)buffer, 16U);
p25step = 0;
break;
}
m_modeinfo.stream_state = TRANSMITTING;
m_modeinfo.srcid = m_dmrid;
m_modeinfo.dstid = m_hostname;
m_modeinfo.frame_number = p25step;
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
}
else{
txdata.append((char *)REC80, 17U);
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
fprintf(stderr, "P25 TX stopped\n");
m_txtimer->stop();
if(m_ttsid == 0){
m_audio->stop_capture();
}
p25step = 0;
m_modeinfo.stream_state = STREAM_IDLE;
m_modeinfo.srcid = 0;
m_modeinfo.dstid = 0;
m_modeinfo.frame_number = 0;
m_txcodecq.clear();
}
emit update_output_level(m_audio->level());
emit update(m_modeinfo);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < txdata.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void P25Codec::process_rx_data()
{
if(m_rxwatchdog++ > 50){
qDebug() << "P25 RX stream timeout ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_LOST;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
uint8_t imbe[11];
int16_t pcm[160];
if(m_rxcodecq.size() > 10){
for(int i = 0; i < 11; ++i){
imbe[i] = m_rxcodecq.dequeue();
}
vocoder.decode_4400(pcm, imbe);
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){
m_rxtimer->stop();
m_audio->stop_playback();
m_rxwatchdog = 0;
m_modeinfo.streamid = 0;
m_rxcodecq.clear();
qDebug() << "P25 playback stopped";
}
}

@ -0,0 +1,47 @@
/*
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/>.
*/
#ifndef P25CODEC_H
#define P25CODEC_H
#include "codec.h"
class P25Codec : public Codec
{
Q_OBJECT
public:
P25Codec(QString callsign, int dmrid, int hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout);
~P25Codec();
unsigned char * get_frame(unsigned char *ambe);
private:
int m_p25cnt;
unsigned char imbe[11U];
int m_hostname;
uint32_t m_dmrid;
uint32_t m_txdstid;
private slots:
void process_udp();
void process_rx_data();
void send_ping();
void send_disconnect();
void transmit();
void hostname_lookup(QHostInfo i);
void dmr_tgid_changed(unsigned int id) { m_txdstid = id; }
void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; }
};
#endif // P25CODEC_H

@ -0,0 +1,11 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>images/droidstar.png</file>
<file>AboutTab.qml</file>
<file>HostsTab.qml</file>
<file>LogTab.qml</file>
<file>SettingsTab.qml</file>
<file>MainTab.qml</file>
</qresource>
</RCC>

@ -0,0 +1,713 @@
/*
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 <iostream>
#include <cstring>
#include "refcodec.h"
#include "CRCenc.h"
#define DEBUG
const unsigned char MMDVM_DSTAR_HEADER = 0x10U;
const unsigned char MMDVM_DSTAR_DATA = 0x11U;
const unsigned char MMDVM_DSTAR_LOST = 0x12U;
const unsigned char MMDVM_DSTAR_EOT = 0x13U;
REFCodec::REFCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) :
Codec(callsign, module, hostname, host, port, ipv6, vocoder, modem, audioin, audioout)
{
}
REFCodec::~REFCodec()
{
}
void REFCodec::process_udp()
{
QByteArray buf;
QByteArray out;
QHostAddress sender;
quint16 senderPort;
static bool sd_sync = 0;
static int sd_seq = 0;
static char user_data[21];
const unsigned char header[5] = {0x80,0x44,0x53,0x56,0x54};
buf.resize(m_udp->pendingDatagramSize());
m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
#ifdef DEBUG
fprintf(stderr, "RECV: ");
for(int i = 0; i < buf.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
if ((buf.size() == 5) && (buf.data()[0] == 5)){
int x = (::rand() % (999999 - 7245 + 1)) + 7245;
QString serial = "HS" + QString("%1").arg(x, 6, 10, QChar('0'));
out.append(0x1c);
out.append(0xc0);
out.append(0x04);
out.append('\x00');
out.append(m_modeinfo.callsign.toUpper().toLocal8Bit().data(), 6);
out.append(10,'\x00');
out.append(serial.toUtf8());
m_udp->writeDatagram(out, m_address, 20001);
}
if(buf.size() == 3){ //2 way keep alive ping
m_modeinfo.count++;
if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){
m_modeinfo.stream_state = STREAM_IDLE;
}
emit update(m_modeinfo);
}
#ifdef DEBUG
if(out.size()){
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
}
#endif
if((m_modeinfo.status == CONNECTING) && (buf.size() == 0x08)){
if((memcmp(&buf.data()[4], "OKRW", 4) == 0) || (memcmp(&buf.data()[4], "OKRO", 4) == 0) || (memcmp(&buf.data()[4], "BUSY", 4) == 0)){
m_modeinfo.vocoder_loaded = load_vocoder_plugin();
if(m_vocoder != ""){
m_hwrx = true;
m_hwtx = true;
m_ambedev = new SerialAMBE("REF");
m_ambedev->connect_to_serial(m_vocoder);
connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe()));
}
else{
m_hwrx = false;
m_hwtx = false;
}
if(m_modemport != ""){
m_modem = new SerialModem("REF");
m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex);
m_modem->set_modem_params(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_modem->connect_to_serial(m_modemport);
connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray)));
}
m_rxtimer = new QTimer();
connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data()));
m_txtimer = new QTimer();
connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit()));
m_ping_timer = new QTimer();
connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping()));
m_ping_timer->start(1000);
m_audio = new AudioEngine(m_audioin, m_audioout);
m_audio->init();
if(buf.data()[7] == 0x57){ //OKRW
m_modeinfo.status = CONNECTED_RW;
//memset(m_rptr2, ' ', 8);
//memcpy(rptr2, hostname.toLocal8Bit(), hostname.size());
//rptr2[7] = module;
//rptr2[8] = 0;
//m_ping_timer->start(1000);
}
else if(buf.data()[7] == 0x4f){ //OKRO -- Go get registered!
m_modeinfo.status = CONNECTED_RW;
}
}
else if((buf.data()[4] == 0x46) && (buf.data()[5] == 0x41) && (buf.data()[6] == 0x49) && (buf.data()[7] == 0x4c)){ // FAIL response
m_modeinfo.status = DISCONNECTED;
}
else{ //Unknown response
m_modeinfo.status = DISCONNECTED;
}
emit update(m_modeinfo);
}
if(m_modeinfo.status != CONNECTED_RW) return;
if((buf.size() == 0x3a) && (!memcmp(buf.data()+1, header, 5)) ){
char temp[9];
memcpy(temp, buf.data() + 20, 8); temp[8] = '\0';
QString rptr2 = QString(temp);
memcpy(temp, buf.data() + 28, 8); temp[8] = '\0';
QString rptr1 = QString(temp);
memcpy(temp, buf.data() + 36, 8); temp[8] = '\0';
QString urcall = QString(temp);
memcpy(temp, buf.data() + 44, 8); temp[8] = '\0';
QString mycall = QString(temp);
QString h = m_hostname + " " + m_module;
qDebug() << "h:r1:r2 == " << h.simplified() << ":" << rptr1.simplified() << ":" << rptr2.simplified();
if( (rptr2.simplified() == h.simplified()) || (rptr1.simplified() == h.simplified()) ){
m_rxwatchdog = 0;
const uint16_t streamid = (buf.data()[14] << 8) | (buf.data()[15] & 0xff);
m_modeinfo.src = mycall;
m_modeinfo.dst = urcall;
m_modeinfo.gw = rptr1;
m_modeinfo.gw2 = rptr2;
if(!m_tx && !m_rxtimer->isActive() && (m_modeinfo.streamid == 0)){
m_audio->start_playback();
m_rxtimer->start(m_rxtimerint);
m_rxcodecq.clear();
m_modeinfo.stream_state = STREAM_NEW;
m_modeinfo.streamid = streamid;
if(m_modem){
uint8_t out[44];
out[0] = 0xe0;
out[1] = 44;
out[2] = MMDVM_DSTAR_HEADER;
out[3] = 0x40;
out[4] = 0;
out[5] = 0;
memcpy(out + 6, rptr2.toLocal8Bit().data(), 8);
memcpy(out + 14, rptr1.toLocal8Bit().data(), 8);
memcpy(out + 22, urcall.toLocal8Bit().data(), 8);
memcpy(out + 30, mycall.toLocal8Bit().data(), 8);
memcpy(out + 38, buf.data() + 52, 4);
CCRC::addCCITT161((uint8_t *)out + 3, 41);
for(int i = 0; i < 44; ++i){
m_rxmodemq.append(out[i]);
}
//m_modem->write(out);
}
qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16);
emit update(m_modeinfo);
}
}
else{
//streamid = 0;
}
}
if((buf.size() == 0x1d) && (!memcmp(buf.data()+1, header, 5)) ){ //29
const uint16_t streamid = (buf.data()[14] << 8) | (buf.data()[15] & 0xff);
//qDebug() << "streamid:s == " << m_streamid << ":" << s;
if(streamid != m_modeinfo.streamid){
return;
}
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAMING;
m_modeinfo.frame_number = buf.data()[16];
if(m_modem){
m_rxmodemq.append(0xe0);
m_rxmodemq.append(15);
m_rxmodemq.append(MMDVM_DSTAR_DATA);
for(int i = 0; i < 12; ++i){
m_rxmodemq.append(buf.data()[17+i]);
}
}
if((buf.data()[16] == 0) && (buf.data()[26] == 0x55) && (buf.data()[27] == 0x2d) && (buf.data()[28] == 0x16)){
sd_sync = 1;
sd_seq = 1;
}
if(sd_sync && (sd_seq == 1) && (buf.data()[16] == 1) && (buf.data()[26] == 0x30)){
user_data[0] = buf.data()[27] ^ 0x4f;
user_data[1] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 2) && (buf.data()[16] == 2)){
user_data[2] = buf.data()[26] ^ 0x70;
user_data[3] = buf.data()[27] ^ 0x4f;
user_data[4] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 3) && (buf.data()[16] == 3) && (buf.data()[26] == 0x31)){
user_data[5] = buf.data()[27] ^ 0x4f;
user_data[6] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 4) && (buf.data()[16] == 4)){
user_data[7] = buf.data()[26] ^ 0x70;
user_data[8] = buf.data()[27] ^ 0x4f;
user_data[9] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 5) && (buf.data()[16] == 5) && (buf.data()[26] == 0x32)){
user_data[10] = buf.data()[27] ^ 0x4f;
user_data[11] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 6) && (buf.data()[16] == 6)){
user_data[12] = buf.data()[26] ^ 0x70;
user_data[13] = buf.data()[27] ^ 0x4f;
user_data[14] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 7) && (buf.data()[16] == 7) && (buf.data()[26] == 0x33)){
user_data[15] = buf.data()[27] ^ 0x4f;
user_data[16] = buf.data()[28] ^ 0x93;
++sd_seq;
}
if(sd_sync && (sd_seq == 8) && (buf.data()[16] == 8)){
user_data[17] = buf.data()[26] ^ 0x70;
user_data[18] = buf.data()[27] ^ 0x4f;
user_data[19] = buf.data()[28] ^ 0x93;
user_data[20] = '\0';
sd_sync = 0;
sd_seq = 0;
m_modeinfo.usertxt = QString(user_data);
//ui->usertxt->setText(QString::fromUtf8(user_data.data()));
}
for(int i = 0; i < 9; ++i){
m_rxcodecq.append(buf.data()[17+i]);
}
emit update(m_modeinfo);
}
if(buf.size() == 0x20){ //32
const uint16_t streamid = (buf.data()[14] << 8) | (buf.data()[15] & 0xff);
if(streamid == m_modeinfo.streamid){
if(m_modem){
m_rxmodemq.append(0xe0);
m_rxmodemq.append(3);
m_rxmodemq.append(MMDVM_DSTAR_EOT);
}
m_modeinfo.usertxt.clear();
qDebug() << "REF RX stream ended ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_END;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
}
//emit update(m_modeinfo);
}
void REFCodec::hostname_lookup(QHostInfo i)
{
if (!i.addresses().isEmpty()) {
QByteArray out;
out.append(0x05);
out.append('\x00');
out.append(0x18);
out.append('\x00');
out.append(0x01);
m_address = i.addresses().first();
m_udp = new QUdpSocket(this);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp()));
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "CONN: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
}
void REFCodec::send_ping()
{
QByteArray out;
out.append(0x03);
out.append(0x60);
out.append('\x00');
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "PING: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void REFCodec::send_disconnect()
{
QByteArray out;
out.append(0x05);
out.append('\x00');
out.append(0x18);
out.append('\x00');
out.append('\x00');
m_udp->writeDatagram(out, m_address, m_modeinfo.port);
#ifdef DEBUG
fprintf(stderr, "SEND: ");
for(int i = 0; i < out.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)out.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void REFCodec::format_callsign(QString &s)
{
QStringList l = s.simplified().split(' ');
if(l.size() > 1){
s = l.at(0).simplified();
while(s.size() < 7){
s.append(' ');
}
s += l.at(1).simplified();
}
else{
while(s.size() < 8){
s.append(' ');
}
}
}
void REFCodec::process_modem_data(QByteArray d)
{
QByteArray txdata;
char cs[9];
uint8_t ambe[9];
uint8_t *p_frame = (uint8_t *)(d.data());
if(p_frame[2] == MMDVM_DSTAR_HEADER){
format_callsign(m_txrptr1);
format_callsign(m_txrptr2);
cs[8] = 0;
memcpy(cs, p_frame + 22, 8);
m_txurcall = QString(cs);
memcpy(cs, p_frame + 30, 8);
m_txmycall = QString(cs);
m_modeinfo.stream_state = TRANSMITTING_MODEM;
m_tx = true;
}
else if( (p_frame[2] == MMDVM_DSTAR_EOT) || (p_frame[2] == MMDVM_DSTAR_LOST) ){
m_tx = false;
}
else if(p_frame[2] == MMDVM_DSTAR_DATA){
memcpy(ambe, p_frame + 3, 9);
}
send_frame(ambe);
}
void REFCodec::toggle_tx(bool tx)
{
tx ? start_tx() : stop_tx();
}
void REFCodec::start_tx()
{
format_callsign(m_txmycall);
format_callsign(m_txurcall);
format_callsign(m_txrptr1);
format_callsign(m_txrptr2);
Codec::start_tx();
}
void REFCodec::transmit()
{
unsigned char ambe[9];
uint8_t ambe_frame[72];
int16_t pcm[160];
memset(ambe_frame, 0, 72);
memset(ambe, 0, 9);
#ifdef USE_FLITE
if(m_ttsid > 0){
for(int i = 0; i < 160; ++i){
if(m_ttscnt >= tts_audio->num_samples/2){
//audiotx_cnt = 0;
pcm[i] = 0;
}
else{
pcm[i] = tts_audio->samples[m_ttscnt*2] / 2;
m_ttscnt++;
}
}
}
#endif
if(m_ttsid == 0){
if(m_audio->read(pcm, 160)){
}
else{
return;
}
}
if(m_hwtx){
m_ambedev->encode(pcm);
if(m_tx && (m_txcodecq.size() >= 9)){
for(int i = 0; i < 9; ++i){
ambe[i] = m_txcodecq.dequeue();
}
send_frame(ambe);
}
else if(!m_tx){
send_frame(ambe);
}
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->encode_2400x1200(pcm, ambe);
}
send_frame(ambe);
}
}
void REFCodec::send_frame(uint8_t *ambe)
{
QByteArray txdata;
static uint16_t txstreamid = 0;
static bool sendheader = 1;
if(txstreamid == 0){
txstreamid = static_cast<uint16_t>((::rand() & 0xFFFF));
//std::cerr << "txstreamid == " << txstreamid << std::endl;
}
if(sendheader){
sendheader = 0;
txdata.resize(58);
txdata[0] = 0x3a;
txdata[1] = 0x80;
txdata[2] = 0x44;
txdata[3] = 0x53;
txdata[4] = 0x56;
txdata[5] = 0x54;
txdata[6] = 0x10;
txdata[7] = 0x00;
txdata[8] = 0x00;
txdata[9] = 0x00;
txdata[10] = 0x20;
txdata[11] = 0x00;
txdata[12] = 0x02;
txdata[13] = 0x01;
txdata[14] = txstreamid & 0xff;
txdata[15] = (txstreamid >> 8) & 0xff;
txdata[16] = 0x80;
txdata[17] = 0x00;
txdata[18] = 0x00;
txdata[19] = 0x00;
txdata.replace(20, 8, m_txrptr2.toLocal8Bit().data());
txdata.replace(28, 8, m_txrptr1.toLocal8Bit().data());
txdata.replace(36, 8, m_txurcall.toLocal8Bit().data());
txdata.replace(44, 8, m_txmycall.toLocal8Bit().data());
txdata.replace(52, 4, "AMBE");
CCRC::addCCITT161((uint8_t *)txdata.data() + 17, 41);
m_modeinfo.src = m_txmycall;
m_modeinfo.dst = m_txurcall;
m_modeinfo.gw = m_txrptr1;
m_modeinfo.gw2 = m_txrptr2;
m_modeinfo.streamid = txstreamid;
m_modeinfo.frame_number = m_txcnt;
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
}
else {
txdata.resize(29);
txdata[0] = 0x1d;
txdata[1] = 0x80;
txdata[2] = 0x44;
txdata[3] = 0x53;
txdata[4] = 0x56;
txdata[5] = 0x54;
txdata[6] = 0x20;
txdata[7] = 0x00;
txdata[8] = 0x00;
txdata[9] = 0x00;
txdata[10] = 0x20;
txdata[11] = 0x00;
txdata[12] = 0x02;
txdata[13] = 0x01;
txdata[14] = txstreamid & 0xff;
txdata[15] = (txstreamid >> 8) & 0xff;
txdata[16] = m_txcnt % 21;
memcpy(txdata.data() + 17, ambe, 9);
//for(int i = 0; i < 9; ++i){
//txdata[17 + i] = ad8dp[(tx_cnt * 9) + i];
//if(ambeq.size()){
// txdata[17 + i] = ambeq.dequeue();
//}
//else{
// txdata[17 + i] = 0;
//}
//}
//memset(txdata.data() + 17, 0x00, 9);
switch(txdata.data()[16]){
case 0:
txdata[26] = 0x55;
txdata[27] = 0x2d;
txdata[28] = 0x16;
break;
case 1:
txdata[26] = 0x40 ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[0] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[1] ^ 0x93;
break;
case 2:
txdata[26] = m_txusrtxt.toLocal8Bit().data()[2] ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[3] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[4] ^ 0x93;
break;
case 3:
txdata[26] = 0x41 ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[5] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[6] ^ 0x93;
break;
case 4:
txdata[26] = m_txusrtxt.toLocal8Bit().data()[7] ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[8] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[9] ^ 0x93;
break;
case 5:
txdata[26] = 0x42 ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[10] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[11] ^ 0x93;
break;
case 6:
txdata[26] = m_txusrtxt.toLocal8Bit().data()[12] ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[13] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[14] ^ 0x93;
break;
case 7:
txdata[26] = 0x43 ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[15] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[16] ^ 0x93;
break;
case 8:
txdata[26] = m_txusrtxt.toLocal8Bit().data()[17] ^ 0x70;
txdata[27] = m_txusrtxt.toLocal8Bit().data()[18] ^ 0x4f;
txdata[28] = m_txusrtxt.toLocal8Bit().data()[19] ^ 0x93;
break;
default:
txdata[26] = 0x16;
txdata[27] = 0x29;
txdata[28] = 0xf5;
break;
}
//if((tx_cnt * 9) >= sizeof(ad8dp)){
// tx_cnt = 0;
//}
if((m_txcnt % 21) == 0){
sendheader = 1;
}
if(m_tx){
m_txcnt++;
}
else{
qDebug() << "TX stopped";
//txdata[0] = 0x20;
//txdata[6] = 0x20;
//txdata[16] = m_txcnt % 21;
memset(txdata.data() + 17, 0, 9);
txdata[26] = 0x55;
txdata[27] = 0x55;
txdata[28] = 0x55;
txdata.append(0x55);
txdata.append(0xc8);
txdata.append(0x7a);
m_txcnt = 0;
txstreamid = 0;
m_modeinfo.streamid = 0;
sendheader = 1;
m_txtimer->stop();
if((m_ttsid == 0) && (m_modeinfo.stream_state == TRANSMITTING) ){
m_audio->stop_capture();
}
m_ttscnt = 0;
m_modeinfo.stream_state = STREAM_IDLE;
}
}
m_udp->writeDatagram(txdata, m_address, m_modeinfo.port);
emit update_output_level(m_audio->level());
update(m_modeinfo);
#ifdef DEBUG
fprintf(stderr, "SEND:%d: ", txdata.size());
for(int i = 0; i < txdata.size(); ++i){
fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
#endif
}
void REFCodec::get_ambe()
{
uint8_t ambe[9];
if(m_ambedev->get_ambe(ambe)){
for(int i = 0; i < 9; ++i){
m_txcodecq.append(ambe[i]);
}
}
}
void REFCodec::process_rx_data()
{
int16_t pcm[160];
uint8_t ambe[9];
if(m_rxwatchdog++ > 50){
qDebug() << "REF RX stream timeout ";
m_rxwatchdog = 0;
m_modeinfo.stream_state = STREAM_LOST;
m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch();
emit update(m_modeinfo);
m_modeinfo.streamid = 0;
}
if(m_rxmodemq.size() > 2){
QByteArray out;
int s = m_rxmodemq[1];
if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){
for(int i = 0; i < s; ++i){
out.append(m_rxmodemq.dequeue());
}
m_modem->write(out);
}
}
if((!m_tx) && (m_rxcodecq.size() > 8) ){
for(int i = 0; i < 9; ++i){
ambe[i] = m_rxcodecq.dequeue();
}
if(m_hwrx){
m_ambedev->decode(ambe);
if(m_ambedev->get_audio(pcm)){
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else{
if(m_modeinfo.vocoder_loaded){
m_mbevocoder->decode_2400x1200(pcm, ambe);
}
else{
memset(pcm, 0, 160 * sizeof(int16_t));
}
m_audio->write(pcm, 160);
emit update_output_level(m_audio->level());
}
}
else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){
m_rxtimer->stop();
m_audio->stop_playback();
m_rxwatchdog = 0;
m_modeinfo.streamid = 0;
m_rxcodecq.clear();
qDebug() << "REF playback stopped";
return;
}
}

@ -0,0 +1,51 @@
/*
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/>.
*/
#ifndef REFCODEC_H
#define REFCODEC_H
#include "codec.h"
class REFCodec : public Codec
{
Q_OBJECT
public:
REFCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout);
~REFCodec();
unsigned char * get_frame(unsigned char *ambe);
private:
QString m_txusrtxt;
uint8_t packet_size;
private slots:
void toggle_tx(bool);
void start_tx();
void process_udp();
void process_modem_data(QByteArray);
void process_rx_data();
void get_ambe();
void send_ping();
void send_disconnect();
void transmit();
void format_callsign(QString &s);
void hostname_lookup(QHostInfo i);
void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; }
void module_changed(int m) { m_module = 0x41 + m; m_modeinfo.streamid = 0; }
void usrtxt_changed(QString t) { m_txusrtxt = t; }
void send_frame(uint8_t *);
};
#endif // REFCODEC_H

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save