mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-23 10:05:46 -05:00
68b833ad97
PacketDemod: Support UTF-8.
596 lines
19 KiB
C++
596 lines
19 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
|
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
|
// //
|
|
// 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 as 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 V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// #include <boost/crc.hpp>
|
|
// #include <boost/program_options.hpp>
|
|
// #include <boost/optional.hpp>
|
|
#include <codec2/codec2.h>
|
|
|
|
#include <QDebug>
|
|
|
|
#include "audio/audiofifo.h"
|
|
#include "util/ax25.h"
|
|
|
|
#include "ax25_frame.h"
|
|
#include "m17demod.h"
|
|
#include "m17demodprocessor.h"
|
|
|
|
M17DemodProcessor* M17DemodProcessor::m_this = nullptr;
|
|
|
|
M17DemodProcessor::M17DemodProcessor() :
|
|
m_packetFrameCounter(0),
|
|
m_displayLSF(true),
|
|
m_noiseBlanker(true),
|
|
m_demod(handle_frame),
|
|
m_audioFifo(nullptr),
|
|
m_audioMute(false),
|
|
m_volume(1.0f),
|
|
m_demodInputMessageQueue(nullptr)
|
|
{
|
|
m_this = this;
|
|
m_codec2 = ::codec2_create(CODEC2_MODE_3200);
|
|
m_audioBuffer.resize(12000);
|
|
m_audioBufferFill = 0;
|
|
m_srcCall = "";
|
|
m_destCall = "";
|
|
m_typeInfo = "";
|
|
m_metadata.fill(0);
|
|
m_hasGNSS = false;
|
|
m_crc = 0;
|
|
m_lsfCount = 0;
|
|
setUpsampling(6); // force upsampling of audio to 48k
|
|
m_demod.diagnostics(diagnostic_callback);
|
|
}
|
|
|
|
M17DemodProcessor::~M17DemodProcessor()
|
|
{
|
|
codec2_destroy(m_codec2);
|
|
}
|
|
|
|
void M17DemodProcessor::pushSample(qint16 sample)
|
|
{
|
|
m_demod(sample / 22000.0f);
|
|
}
|
|
|
|
bool M17DemodProcessor::handle_frame(modemm17::M17FrameDecoder::output_buffer_t const& frame, int viterbi_cost)
|
|
{
|
|
using FrameType = modemm17::M17FrameDecoder::FrameType;
|
|
|
|
bool result = true;
|
|
|
|
switch (frame.type)
|
|
{
|
|
case FrameType::LSF:
|
|
result = m_this->decode_lsf(frame.lsf);
|
|
break;
|
|
case FrameType::LICH:
|
|
result = m_this->decode_lich(frame.lich);
|
|
break;
|
|
case FrameType::STREAM:
|
|
result = m_this->demodulate_audio(frame.stream, viterbi_cost);
|
|
break;
|
|
case FrameType::BASIC_PACKET:
|
|
result = m_this->decode_packet(frame.packet);
|
|
break;
|
|
case FrameType::FULL_PACKET:
|
|
result = m_this->decode_packet(frame.packet);
|
|
break;
|
|
case FrameType::BERT:
|
|
result = m_this->decode_bert(frame.bert);
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void M17DemodProcessor::diagnostic_callback(
|
|
bool dcd,
|
|
float evm,
|
|
float deviation,
|
|
float offset,
|
|
int status,
|
|
int sync_word_type,
|
|
float clock,
|
|
int sample_index,
|
|
int sync_index,
|
|
int clock_index,
|
|
int viterbi_cost)
|
|
{
|
|
bool debug = false;
|
|
bool quiet = true;
|
|
|
|
m_this->m_dcd = dcd;
|
|
m_this->m_evm = evm;
|
|
m_this->m_deviation = deviation;
|
|
m_this->m_offset = offset;
|
|
m_this->m_status = status;
|
|
m_this->m_syncWordType = sync_word_type;
|
|
m_this->m_clock = clock;
|
|
m_this->m_sampleIndex = sample_index;
|
|
m_this->m_syncIndex = sync_index;
|
|
m_this->m_clockIndex = clock_index;
|
|
m_this->m_viterbiCost = viterbi_cost;
|
|
|
|
if (debug)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "dcd: " << std::setw(1) << int(dcd)
|
|
<< ", evm: " << std::setfill(' ') << std::setprecision(4) << std::setw(8) << evm * 100 <<"%"
|
|
<< ", deviation: " << std::setprecision(4) << std::setw(8) << deviation
|
|
<< ", freq offset: " << std::setprecision(4) << std::setw(8) << offset
|
|
<< ", locked: " << std::boolalpha << std::setw(6) << (status != 0) << std::dec
|
|
<< ", clock: " << std::setprecision(7) << std::setw(8) << clock
|
|
<< ", sample: " << std::setw(1) << sample_index << ", " << sync_index << ", " << clock_index
|
|
<< ", cost: " << viterbi_cost;
|
|
qDebug() << "M17DemodProcessor::diagnostic_callback: " << oss.str().c_str();
|
|
}
|
|
|
|
if (m_this->m_prbs.sync() && !quiet)
|
|
{
|
|
std::ostringstream oss;
|
|
auto ber = double(m_this->m_prbs.errors()) / double(m_this->m_prbs.bits());
|
|
char buffer[40];
|
|
snprintf(buffer, 40, "BER: %-1.6lf (%u bits)", ber, m_this->m_prbs.bits());
|
|
oss << buffer;
|
|
qDebug() << "M17DemodProcessor::diagnostic_callback: " << oss.str().c_str();
|
|
}
|
|
}
|
|
|
|
bool M17DemodProcessor::decode_lich(modemm17::M17FrameDecoder::lich_buffer_t const& lich)
|
|
{
|
|
uint8_t fragment_number = lich[5]; // Get fragment number.
|
|
fragment_number = (fragment_number >> 5) & 7;
|
|
qDebug("M17DemodProcessor::handle_frame: LICH: %d", (int) fragment_number);
|
|
return true;
|
|
}
|
|
|
|
bool M17DemodProcessor::decode_lsf(modemm17::M17FrameDecoder::lsf_buffer_t const& lsf)
|
|
{
|
|
modemm17::LinkSetupFrame::encoded_call_t encoded_call;
|
|
std::ostringstream oss;
|
|
|
|
std::copy(lsf.begin() + 6, lsf.begin() + 12, encoded_call.begin());
|
|
modemm17::LinkSetupFrame::call_t src = modemm17::LinkSetupFrame::decode_callsign(encoded_call);
|
|
m_srcCall = QString(src.data());
|
|
|
|
std::copy(lsf.begin(), lsf.begin() + 6, encoded_call.begin());
|
|
modemm17::LinkSetupFrame::call_t dest = modemm17::LinkSetupFrame::decode_callsign(encoded_call);
|
|
m_destCall = QString(dest.data());
|
|
|
|
uint16_t type = (lsf[12] << 8) | lsf[13];
|
|
decode_type(type);
|
|
|
|
m_hasGNSS = ((lsf[13] >> 5) & 3) == 1;
|
|
|
|
std::copy(lsf.begin()+14, lsf.begin()+28, m_metadata.begin());
|
|
m_crc = (lsf[28] << 8) | lsf[29];
|
|
|
|
if (m_displayLSF)
|
|
{
|
|
oss << "SRC: " << m_srcCall.toStdString().c_str();
|
|
oss << ", DEST: " << m_destCall.toStdString().c_str();
|
|
oss << ", " << m_typeInfo.toStdString().c_str();
|
|
oss << ", META: ";
|
|
for (size_t i = 0; i != 14; ++i) {
|
|
oss << std::hex << std::setw(2) << std::setfill('0') << int(m_metadata[i]);
|
|
}
|
|
oss << ", CRC: " << std::hex << std::setw(4) << std::setfill('0') << m_crc;
|
|
oss << std::dec;
|
|
}
|
|
|
|
m_currentPacket.clear();
|
|
m_packetFrameCounter = 0;
|
|
|
|
if (!(lsf[13] & 1)) // LSF type bit 0
|
|
{
|
|
uint8_t packet_type = (lsf[13] >> 1) & 0x3;
|
|
|
|
switch (packet_type)
|
|
{
|
|
case 1: // RAW -- ignore LSF.
|
|
break;
|
|
case 2: // ENCAPSULATED
|
|
append_packet(m_currentPacket, lsf);
|
|
break;
|
|
default:
|
|
oss << " LSF for reserved packet type";
|
|
append_packet(m_currentPacket, lsf);
|
|
}
|
|
}
|
|
|
|
m_lsfCount++;
|
|
qDebug() << "M17DemodProcessor::decode_lsf: " << m_lsfCount << ":" << oss.str().c_str();
|
|
return true;
|
|
}
|
|
|
|
void M17DemodProcessor::decode_type(uint16_t type)
|
|
{
|
|
m_streamElsePacket = type & 1; // bit 0
|
|
|
|
if (m_streamElsePacket)
|
|
{
|
|
m_typeInfo = "STR:"; // Stream mode
|
|
|
|
switch ((type & 6) >> 1) // bits 1..2
|
|
{
|
|
case 0:
|
|
m_typeInfo += "UNK";
|
|
break;
|
|
case 1:
|
|
m_typeInfo += "D/D";
|
|
break;
|
|
case 2:
|
|
m_typeInfo += "V/V";
|
|
break;
|
|
case 3:
|
|
m_typeInfo += "V/D";
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_typeInfo = "PKT:"; // Packet mode
|
|
|
|
switch ((type & 6) >> 1) // bits 1..2
|
|
{
|
|
case 0:
|
|
m_typeInfo += "UNK";
|
|
break;
|
|
case 1:
|
|
m_typeInfo += "DAT";
|
|
break;
|
|
case 2:
|
|
m_typeInfo += "ENC"; // Encapsulated passes LSF up stack along with data
|
|
break;
|
|
case 3:
|
|
m_typeInfo += "UNK";
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_typeInfo += QString(" CAN:%1").arg(int((type & 0x780) >> 7), 2, 10, QChar('0')); // Channel Access number (bits 7..10)
|
|
}
|
|
|
|
void M17DemodProcessor::resetInfo()
|
|
{
|
|
m_srcCall = "";
|
|
m_destCall = "";
|
|
m_typeInfo = "";
|
|
m_streamElsePacket = true;
|
|
m_metadata.fill(0);
|
|
m_crc = 0;
|
|
m_lsfCount = 0;
|
|
}
|
|
|
|
void M17DemodProcessor::setDCDOff()
|
|
{
|
|
m_demod.dcd_off();
|
|
}
|
|
|
|
void M17DemodProcessor::append_packet(std::vector<uint8_t>& result, modemm17::M17FrameDecoder::lsf_buffer_t in)
|
|
{
|
|
uint8_t out = 0;
|
|
size_t b = 0;
|
|
|
|
for (auto c : in)
|
|
{
|
|
out = (out << 1) | c;
|
|
if (++b == 8)
|
|
{
|
|
result.push_back(out);
|
|
out = 0;
|
|
b = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool M17DemodProcessor::decode_packet(modemm17::M17FrameDecoder::packet_buffer_t const& packet_segment)
|
|
{
|
|
// qDebug() << tr("M17DemodProcessor::decode_packet: 0x%1").arg((int) packet_segment[25], 2, 16, QChar('0'));
|
|
if (packet_segment[25] & 0x80) // last frame of packet.
|
|
{
|
|
size_t packet_size = (packet_segment[25] & 0x7F) >> 2;
|
|
packet_size = std::min(packet_size, size_t(25)); // on last frame this is the remainder byte count
|
|
m_packetFrameCounter = 0;
|
|
|
|
for (size_t i = 0; i < packet_size; ++i) {
|
|
m_currentPacket.push_back(packet_segment[i]);
|
|
}
|
|
|
|
if (m_currentPacket.size() < 3)
|
|
{
|
|
qDebug() << "M17DemodProcessor::decode_packet: too small:" << m_currentPacket.size();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "M17DemodProcessor::decode_packet: last chunk size:" << packet_size << " packet size:" << m_currentPacket.size();
|
|
}
|
|
|
|
modemm17::CRC16 crc16(0x5935, 0xFFFF);
|
|
crc16.reset();
|
|
|
|
for (std::vector<uint8_t>::const_iterator it = m_currentPacket.begin(); it != m_currentPacket.end() - 2; ++it) {
|
|
crc16(*it);
|
|
}
|
|
|
|
uint16_t calcChecksum = crc16.get_bytes()[1] + (crc16.get_bytes()[0]<<8);
|
|
uint16_t xmitChecksum = m_currentPacket.back() + (m_currentPacket.end()[-2]<<8);
|
|
|
|
if (calcChecksum == xmitChecksum) // (checksum == 0x0f47)
|
|
{
|
|
uint8_t protocol = m_currentPacket.front();
|
|
m_stdPacketProtocol = protocol < (int) StdPacketUnknown ? (StdPacketProtocol) protocol : StdPacketUnknown;
|
|
qDebug() << "M17DemodProcessor::decode_packet: protocol: " << m_stdPacketProtocol;
|
|
|
|
if (m_stdPacketProtocol == StdPacketAX25)
|
|
{
|
|
std::string ax25;
|
|
ax25.reserve(m_currentPacket.size());
|
|
|
|
for (auto c : m_currentPacket) {
|
|
ax25.push_back(char(c));
|
|
}
|
|
|
|
modemm17::ax25_frame frame(ax25);
|
|
std::ostringstream oss;
|
|
modemm17::write(oss, frame); // TODO: get details
|
|
qDebug() << "M17DemodProcessor::decode_packet: AX25:" << oss.str().c_str();
|
|
}
|
|
else if (m_stdPacketProtocol == StdPacketSMS)
|
|
{
|
|
std::ostringstream oss;
|
|
|
|
for (std::vector<uint8_t>::const_iterator it = m_currentPacket.begin()+1; it < m_currentPacket.end()-2; ++it) {
|
|
oss << *it;
|
|
}
|
|
|
|
qDebug() << "M17DemodProcessor::decode SMS_packet: "
|
|
<< " Src:" << getSrcCall()
|
|
<< " Dest:" << getDestcCall()
|
|
<< " SMS:" << oss.str().c_str();
|
|
|
|
if (m_demodInputMessageQueue)
|
|
{
|
|
M17Demod::MsgReportSMS *msg = M17Demod::MsgReportSMS::create(
|
|
getSrcCall(),
|
|
getDestcCall(),
|
|
QString(oss.str().c_str())
|
|
);
|
|
m_demodInputMessageQueue->push(msg);
|
|
}
|
|
}
|
|
else if (m_stdPacketProtocol == StdPacketAPRS)
|
|
{
|
|
AX25Packet ax25;
|
|
QByteArray packet = QByteArray(reinterpret_cast<const char*>(&m_currentPacket[1]), m_currentPacket.size()-3);
|
|
|
|
if (ax25.decode(packet))
|
|
{
|
|
qDebug() << "M17DemodProcessor::decode APRS_packet: "
|
|
<< " Src:" << getSrcCall()
|
|
<< " Dest:" << getDestcCall()
|
|
<< " From:" << ax25.m_from
|
|
<< " To: " << ax25.m_to
|
|
<< " Via: " << ax25.m_via
|
|
<< " Type: " << ax25.m_type
|
|
<< " PID: " << ax25.m_pid
|
|
<< " Data: " << QString::fromUtf8(ax25.m_data);
|
|
|
|
if (m_demodInputMessageQueue)
|
|
{
|
|
M17Demod::MsgReportAPRS *msg = M17Demod::MsgReportAPRS::create(
|
|
getSrcCall(),
|
|
getDestcCall(),
|
|
ax25.m_from,
|
|
ax25.m_to,
|
|
ax25.m_via,
|
|
ax25.m_type,
|
|
ax25.m_pid,
|
|
ax25.m_data
|
|
);
|
|
msg->getPacket() = packet;
|
|
m_demodInputMessageQueue->push(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QString ccrc = tr("0x%1").arg(calcChecksum, 4, 16, QChar('0'));
|
|
QString xcrc = tr("0x%1").arg(xmitChecksum, 4, 16, QChar('0'));
|
|
qWarning() << "M17DemodProcessor::decode_packet: Packet checksum error: " << ccrc << "vs" << xcrc;
|
|
return false;
|
|
}
|
|
|
|
size_t frame_number = (packet_segment[25] & 0x7F) >> 2;
|
|
|
|
if (frame_number != m_packetFrameCounter)
|
|
{
|
|
qWarning() << "M17DemodProcessor::decode_packet: Packet frame sequence error. Got "
|
|
<< frame_number << ", expected " << m_packetFrameCounter;
|
|
return false;
|
|
}
|
|
|
|
if (m_packetFrameCounter == 0) {
|
|
m_currentPacket.clear();
|
|
}
|
|
|
|
for (size_t i = 0; i < 25; ++i) {
|
|
m_currentPacket.push_back(packet_segment[i]);
|
|
}
|
|
|
|
m_packetFrameCounter++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool M17DemodProcessor::decode_bert(modemm17::M17FrameDecoder::bert_buffer_t const& bert)
|
|
{
|
|
for (int j = 0; j != 24; ++j)
|
|
{
|
|
auto b = bert[j];
|
|
|
|
for (int i = 0; i != 8; ++i)
|
|
{
|
|
m_prbs.validate(b & 0x80);
|
|
b <<= 1;
|
|
}
|
|
}
|
|
|
|
auto b = bert[24];
|
|
|
|
for (int i = 0; i != 5; ++i)
|
|
{
|
|
m_prbs.validate(b & 0x80);
|
|
b <<= 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool M17DemodProcessor::demodulate_audio(modemm17::M17FrameDecoder::audio_buffer_t const& audio, int viterbi_cost)
|
|
{
|
|
bool result = true;
|
|
std::array<int16_t, 160> buf; // 8k audio
|
|
|
|
// First two bytes are the frame counter + EOS indicator.
|
|
if (viterbi_cost < 70 && (audio[0] & 0x80))
|
|
{
|
|
if (m_displayLSF) {
|
|
qDebug() << "M17DemodProcessor::demodulate_audio: EOS";
|
|
}
|
|
|
|
result = false;
|
|
}
|
|
|
|
if (m_audioFifo && !m_audioMute)
|
|
{
|
|
if (m_noiseBlanker && viterbi_cost > 80)
|
|
{
|
|
buf.fill(0);
|
|
processAudio(buf); // first block expanded
|
|
processAudio(buf); // second block expanded
|
|
}
|
|
else
|
|
{
|
|
codec2_decode(m_codec2, buf.data(), audio.data() + 2); // first 8 bytes block input
|
|
processAudio(buf);
|
|
codec2_decode(m_codec2, buf.data(), audio.data() + 10); // second 8 bytes block input
|
|
processAudio(buf);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void M17DemodProcessor::setUpsampling(int upsampling)
|
|
{
|
|
m_upsampling = upsampling < 1 ? 1 : upsampling > 6 ? 6 : upsampling;
|
|
}
|
|
|
|
void M17DemodProcessor::setVolume(float volume)
|
|
{
|
|
m_volume = volume;
|
|
setVolumeFactors();
|
|
}
|
|
|
|
void M17DemodProcessor::processAudio(const std::array<int16_t, 160>& in)
|
|
{
|
|
if (m_upsampling > 1) {
|
|
upsample(m_upsampling, in.data(), in.size());
|
|
} else {
|
|
noUpsample(in.data(), in.size());
|
|
}
|
|
|
|
if (m_audioBufferFill >= m_audioBuffer.size() - 960)
|
|
{
|
|
std::size_t res = m_audioFifo->write((const quint8*)&m_audioBuffer[0], std::min(m_audioBufferFill, m_audioBuffer.size()));
|
|
|
|
if (res != m_audioBufferFill) {
|
|
qDebug("M17DemodProcessor::processAudio: %lu/%lu audio samples written", res, m_audioBufferFill);
|
|
}
|
|
|
|
m_audioBufferFill = 0;
|
|
}
|
|
}
|
|
|
|
void M17DemodProcessor::upsample(int upsampling, const int16_t *in, int nbSamplesIn)
|
|
{
|
|
for (int i = 0; i < nbSamplesIn; i++)
|
|
{
|
|
float cur = m_upsamplingFilter.usesHP() ? m_upsamplingFilter.runHP((float) in[i]) : (float) in[i];
|
|
float prev = m_upsamplerLastValue;
|
|
qint16 upsample;
|
|
|
|
for (int j = 1; j <= upsampling; j++)
|
|
{
|
|
upsample = (qint16) m_upsamplingFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]);
|
|
m_audioBuffer[m_audioBufferFill].l = m_compressor.compress(upsample);
|
|
m_audioBuffer[m_audioBufferFill].r = m_compressor.compress(upsample);
|
|
|
|
if (m_audioBufferFill < m_audioBuffer.size() - 1) {
|
|
++m_audioBufferFill;
|
|
}
|
|
}
|
|
|
|
m_upsamplerLastValue = cur;
|
|
}
|
|
|
|
if (m_audioBufferFill >= m_audioBuffer.size() - 1) {
|
|
qDebug("M17DemodProcessor::upsample(%d): audio buffer is full check its size", upsampling);
|
|
}
|
|
}
|
|
|
|
void M17DemodProcessor::noUpsample(const int16_t *in, int nbSamplesIn)
|
|
{
|
|
for (int i = 0; i < nbSamplesIn; i++)
|
|
{
|
|
float cur = m_upsamplingFilter.usesHP() ? m_upsamplingFilter.runHP((float) in[i]) : (float) in[i];
|
|
m_audioBuffer[m_audioBufferFill].l = cur*m_upsamplingFactors[0];
|
|
m_audioBuffer[m_audioBufferFill].r = cur*m_upsamplingFactors[0];
|
|
|
|
if (m_audioBufferFill < m_audioBuffer.size() - 1) {
|
|
++m_audioBufferFill;
|
|
}
|
|
}
|
|
|
|
if (m_audioBufferFill >= m_audioBuffer.size() - 1) {
|
|
qDebug("M17DemodProcessor::noUpsample: audio buffer is full check its size");
|
|
}
|
|
}
|
|
|
|
void M17DemodProcessor::setVolumeFactors()
|
|
{
|
|
m_upsamplingFactors[0] = m_volume;
|
|
|
|
for (int i = 1; i <= m_upsampling; i++) {
|
|
m_upsamplingFactors[i] = (i*m_volume) / (float) m_upsampling;
|
|
}
|
|
}
|
|
|
|
M17DemodProcessor::StdPacketProtocol M17DemodProcessor::getStdPacketProtocol() const
|
|
{
|
|
if (m_streamElsePacket) {
|
|
return StdPacketUnknown;
|
|
} else {
|
|
return m_stdPacketProtocol;
|
|
}
|
|
}
|