You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
847 lines
22 KiB
C++
847 lines
22 KiB
C++
/*
|
|
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 "iax.h"
|
|
#include "iaxdefines.h"
|
|
#ifdef Q_OS_WIN
|
|
#include <winsock2.h>
|
|
#else
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#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
|
|
|
|
#define DEBUG
|
|
|
|
IAX::IAX() :
|
|
m_scallno(0),
|
|
m_dcallno(0),
|
|
m_regscallno(0x7fff),
|
|
m_regdcallno(0),
|
|
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
|
|
}
|
|
|
|
IAX::~IAX()
|
|
{
|
|
}
|
|
|
|
void IAX::set_iax_params(QString username, QString password, QString node, QString host, int port)
|
|
{
|
|
m_username = username;
|
|
m_password = password;
|
|
m_node = node;
|
|
m_host = host;
|
|
m_port = port;
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
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 IAX::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);
|
|
|
|
if(m_debug){
|
|
QDebug debug = qDebug();
|
|
debug.noquote();
|
|
QString s = "SEND:";
|
|
for(int i = 0; i < out.size(); ++i){
|
|
s += " " + QString("%1").arg((uint8_t)out.data()[i], 2, 16, QChar('0'));
|
|
}
|
|
debug << s;
|
|
}
|
|
}
|
|
|
|
void IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::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 IAX::send_connect()
|
|
{
|
|
m_modeinfo.status = CONNECTING;
|
|
qDebug() << "lookup IP = " << m_host << ":" << m_port;
|
|
QHostInfo::lookupHost(m_host, this, SLOT(hostname_lookup(QHostInfo)));
|
|
}
|
|
|
|
void IAX::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_modeinfo.status == CONNECTING){
|
|
send_call();
|
|
}
|
|
}
|
|
else if((buf.data()[0] & 0x80) &&
|
|
(buf.data()[10] == AST_FRAME_IAX) &&
|
|
(buf.data()[11] == IAX_COMMAND_REGREJ) )
|
|
{
|
|
m_modeinfo.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_modeinfo.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_modeinfo.status == CONNECTING){
|
|
m_modeinfo.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_modeinfo.sw_vocoder_loaded = true;
|
|
}
|
|
++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(m_modeinfo);
|
|
}
|
|
|
|
void IAX::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 IAX::toggle_tx(bool tx)
|
|
{
|
|
qDebug() << "IAXCodec::toggle_tx(bool tx) == " << tx;
|
|
tx ? start_tx() : stop_tx();
|
|
}
|
|
|
|
void IAX::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 IAX::stop_tx()
|
|
{
|
|
m_tx = false;
|
|
send_radio_key(false);
|
|
//QByteArray tx("#", 1);
|
|
//send_dtmf(tx);
|
|
}
|
|
|
|
void IAX::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] / 8;
|
|
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 IAX::deleteLater()
|
|
{
|
|
if(m_modeinfo.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();
|
|
}
|